From 17a2de802c3934979efd8f1f4b660f33653aceab Mon Sep 17 00:00:00 2001 From: Savolro Date: Thu, 17 Oct 2024 12:06:06 +0300 Subject: [PATCH] Improve HeartBeat events * HeartBeat events are now sent once meshnet setting updates * `is_on_vpn` value is always updated after connect and disconnect Signed-off-by: Savolro --- cmd/daemon/events_linux.go | 4 +++- cmd/daemon/main.go | 8 +++++++- daemon/events/events.go | 4 ---- daemon/events/events_test.go | 1 - daemon/job_heartbeat.go | 10 ++++++---- daemon/jobs.go | 11 +++++++++-- events/moose/moose.go | 33 ++++++++++++++++++++++++--------- 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/cmd/daemon/events_linux.go b/cmd/daemon/events_linux.go index 7b3e33df1..cd7fb79f8 100644 --- a/cmd/daemon/events_linux.go +++ b/cmd/daemon/events_linux.go @@ -3,6 +3,8 @@ package main import ( + "time" + "github.com/NordSecurity/nordvpn-linux/config" "github.com/NordSecurity/nordvpn-linux/core" "github.com/NordSecurity/nordvpn-linux/events" @@ -34,7 +36,7 @@ func (*dummyAnalytics) NotifyMFA(bool) error { return n func (*dummyAnalytics) NotifyAccountCheck(any) error { return nil } func (*dummyAnalytics) NotifyRequestAPI(events.DataRequestAPI) error { return nil } func (*dummyAnalytics) NotifyUiItemsClick(events.UiItemsAction) error { return nil } -func (*dummyAnalytics) NotifyHeartBeat(int) error { return nil } +func (*dummyAnalytics) NotifyHeartBeat(time.Duration) error { return nil } func (*dummyAnalytics) NotifyDeviceLocation(core.Insights) error { return nil } func (*dummyAnalytics) NotifyLANDiscovery(bool) error { return nil } func (*dummyAnalytics) NotifyVirtualLocation(bool) error { return nil } diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 374abdc8f..e730ba08a 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -15,6 +15,7 @@ import ( "path/filepath" "runtime" "strconv" + "time" "golang.org/x/net/netutil" @@ -149,6 +150,7 @@ func main() { debugSubject := &subs.Subject[string]{} infoSubject := &subs.Subject[string]{} errSubject := &subs.Subject[error]{} + heartBeatSubject := &subs.Subject[time.Duration]{} httpCallsSubject := &subs.Subject[events.DataRequestAPI]{} loggerSubscriber := logger.Subscriber{} @@ -290,6 +292,10 @@ func main() { daemonEvents.Service.Connect.Subscribe(loggerSubscriber.NotifyConnect) daemonEvents.Settings.Publish(cfg) + // Subscribing after initial settings publishing ensures that heartbeat is not sent with + // the first `NotifyMeshnet` call but will be sent on the first heartbeat job. + heartBeatSubject.Subscribe(analytics.NotifyHeartBeat) + if fsystem.NewInstallation { daemonEvents.Service.UiItemsClick.Publish(events.UiItemsAction{ItemName: "first_open", ItemType: "button", ItemValue: "first_open", FormReference: "daemon"}) } @@ -556,7 +562,7 @@ func main() { log.Println(internal.WarningPrefix, err) } }() - rpc.StartJobs(statePublisher) + rpc.StartJobs(statePublisher, heartBeatSubject) meshService.StartJobs() rpc.StartKillSwitch() if internal.IsSystemd() { diff --git a/daemon/events/events.go b/daemon/events/events.go index 6a7c72d91..08a509bfa 100644 --- a/daemon/events/events.go +++ b/daemon/events/events.go @@ -99,7 +99,6 @@ func NewEvents( Disconnect: disconnect, AccountCheck: accountCheck, UiItemsClick: uiItemsClick, - HeartBeat: heartBeat, DeviceLocation: deviceLocation, }, User: &LoginEvents{ @@ -187,7 +186,6 @@ type ServicePublisher interface { NotifyDisconnect(events.DataDisconnect) error NotifyAccountCheck(any) error NotifyUiItemsClick(events.UiItemsAction) error - NotifyHeartBeat(int) error NotifyDeviceLocation(core.Insights) error } @@ -196,7 +194,6 @@ type ServiceEvents struct { Disconnect events.PublishSubcriber[events.DataDisconnect] AccountCheck events.PublishSubcriber[any] UiItemsClick events.PublishSubcriber[events.UiItemsAction] - HeartBeat events.PublishSubcriber[int] DeviceLocation events.PublishSubcriber[core.Insights] } @@ -205,7 +202,6 @@ func (s *ServiceEvents) Subscribe(to ServicePublisher) { s.Disconnect.Subscribe(to.NotifyDisconnect) s.AccountCheck.Subscribe(to.NotifyAccountCheck) s.UiItemsClick.Subscribe(to.NotifyUiItemsClick) - s.HeartBeat.Subscribe(to.NotifyHeartBeat) s.DeviceLocation.Subscribe(to.NotifyDeviceLocation) } diff --git a/daemon/events/events_test.go b/daemon/events/events_test.go index fb087abba..d5980496f 100644 --- a/daemon/events/events_test.go +++ b/daemon/events/events_test.go @@ -47,7 +47,6 @@ func (mockDaemonSubscriber) NotifyIpv6(bool) error { ret func (mockDaemonSubscriber) NotifyDefaults(any) error { return nil } func (mockDaemonSubscriber) NotifyMeshnet(bool) error { return nil } func (mockDaemonSubscriber) NotifyUiItemsClick(events.UiItemsAction) error { return nil } -func (mockDaemonSubscriber) NotifyHeartBeat(int) error { return nil } func (mockDaemonSubscriber) NotifyDeviceLocation(core.Insights) error { return nil } func (mockDaemonSubscriber) NotifyLANDiscovery(bool) error { return nil } func (mockDaemonSubscriber) NotifyVirtualLocation(bool) error { return nil } diff --git a/daemon/job_heartbeat.go b/daemon/job_heartbeat.go index 0c50fb9d2..903c96353 100644 --- a/daemon/job_heartbeat.go +++ b/daemon/job_heartbeat.go @@ -1,15 +1,17 @@ package daemon import ( - "github.com/NordSecurity/nordvpn-linux/daemon/events" + "time" + + "github.com/NordSecurity/nordvpn-linux/events" ) // JobHeartBeat sends heart beats. func JobHeartBeat( - timePeriod int, - events *events.Events, + publisher events.Publisher[time.Duration], + period time.Duration, ) func() { return func() { - events.Service.HeartBeat.Publish(timePeriod) + publisher.Publish(period) } } diff --git a/daemon/jobs.go b/daemon/jobs.go index e46e641d8..403af04f3 100644 --- a/daemon/jobs.go +++ b/daemon/jobs.go @@ -21,7 +21,14 @@ import ( "google.golang.org/grpc/metadata" ) -func (r *RPC) StartJobs(statePublisher *state.StatePublisher) { +const ( + heartBeatPeriod = time.Hour * 24 +) + +func (r *RPC) StartJobs( + statePublisher *state.StatePublisher, + heartBeatPublisher events.Publisher[time.Duration], +) { // order of the jobs below matters // servers job requires geo info and configs data to create server list // TODO what if configs file is deleted just before servers job or disk is full? @@ -51,7 +58,7 @@ func (r *RPC) StartJobs(statePublisher *state.StatePublisher) { log.Println(internal.WarningPrefix, "job version schedule error:", err) } - if _, err := r.scheduler.NewJob(gocron.DurationJob(24*time.Hour), gocron.NewTask(JobHeartBeat(1*24*60 /*minutes*/, r.events)), gocron.WithName("job heart beat")); err != nil { + if _, err := r.scheduler.NewJob(gocron.DurationJob(heartBeatPeriod), gocron.NewTask(JobHeartBeat(heartBeatPublisher, heartBeatPeriod), gocron.WithName("job heart beat"))); err != nil { log.Println(internal.WarningPrefix, "job heart beat schedule error:", err) } if _, err := r.scheduler.NewJob(gocron.DurationJob(7*24*time.Hour), gocron.NewTask(func() { diff --git a/events/moose/moose.go b/events/moose/moose.go index ecce7564f..695e3d8bd 100644 --- a/events/moose/moose.go +++ b/events/moose/moose.go @@ -346,8 +346,8 @@ func (s *Subscriber) NotifyUiItemsClick(data events.UiItemsAction) error { )) } -func (s *Subscriber) NotifyHeartBeat(timePeriodMinutes int) error { - return s.response(moose.NordvpnappSendServiceQualityStatusHeartbeat(int32(timePeriodMinutes))) +func (s *Subscriber) NotifyHeartBeat(period time.Duration) error { + return s.response(moose.NordvpnappSendServiceQualityStatusHeartbeat(int32(period.Minutes()))) } func (s *Subscriber) NotifyDeviceLocation(insights core.Insights) error { @@ -369,7 +369,11 @@ func (s *Subscriber) NotifyDeviceLocation(insights core.Insights) error { func (s *Subscriber) NotifyNotify(bool) error { return nil } func (s *Subscriber) NotifyMeshnet(data bool) error { - return s.response(moose.NordvpnappSetContextApplicationNordvpnappConfigUserPreferencesMeshnetEnabledValue(data)) + if err := s.response(moose.NordvpnappSetContextApplicationNordvpnappConfigUserPreferencesMeshnetEnabledValue(data)); err != nil { + return err + } + // 0 duration indicates that this is not a periodic heart beat + return s.NotifyHeartBeat(time.Duration(0)) } func (s *Subscriber) NotifyObfuscate(data bool) error { @@ -499,7 +503,7 @@ func (s *Subscriber) NotifyConnect(data events.DataConnect) error { default: rule = moose.NordvpnappServerSelectionRuleRecommended } - return s.response(moose.NordvpnappSendServiceQualityServersConnect( + if err := s.response(moose.NordvpnappSendServiceQualityServersConnect( int32(data.DurationMs), eventStatus, moose.NordvpnappEventTriggerUser, @@ -518,7 +522,13 @@ func (s *Subscriber) NotifyConnect(data events.DataConnect) error { int32(-1), "", int32(-1), - )) + )); err != nil { + return err + } + if data.EventStatus == events.StatusSuccess { + return s.response(moose.NordvpnappSetContextApplicationNordvpnappConfigCurrentStateIsOnVpnValue(true)) + } + return nil } } @@ -539,13 +549,15 @@ func (s *Subscriber) NotifyDisconnect(data events.DataDisconnect) error { } if s.connectionToMeshnetPeer { - return s.response(moose.NordvpnappSendServiceQualityServersDisconnectFromMeshnetDevice( + if err := s.response(moose.NordvpnappSendServiceQualityServersDisconnectFromMeshnetDevice( int32(-1), eventStatus, moose.NordvpnappEventTriggerUser, connectionTime, // seconds int32(-1), - )) + )); err != nil { + return err + } } else { var technology moose.NordvpnappVpnConnectionTechnology switch data.Technology { @@ -591,7 +603,7 @@ func (s *Subscriber) NotifyDisconnect(data events.DataDisconnect) error { threatProtection = moose.NordvpnappOptBoolFalse } - return s.response(moose.NordvpnappSendServiceQualityServersDisconnect( + if err := s.response(moose.NordvpnappSendServiceQualityServersDisconnect( int32(-1), eventStatus, moose.NordvpnappEventTriggerUser, @@ -610,8 +622,11 @@ func (s *Subscriber) NotifyDisconnect(data events.DataDisconnect) error { connectionTime, // seconds "", int32(-1), - )) + )); err != nil { + return err + } } + return s.response(moose.NordvpnappSetContextApplicationNordvpnappConfigCurrentStateIsOnVpnValue(false)) } func (s *Subscriber) NotifyRequestAPI(data events.DataRequestAPI) error {