From caa7677e14588c872d1441178c0ca1d6ae74cfcc Mon Sep 17 00:00:00 2001 From: VishalDalwadi Date: Fri, 31 Jul 2026 10:26:14 +0530 Subject: [PATCH 1/3] wip(go): add power event registration utils; --- ncutils/power_darwin.go | 11 +++++ ncutils/power_linux.go | 11 +++++ ncutils/power_windows.go | 99 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 ncutils/power_darwin.go create mode 100644 ncutils/power_linux.go create mode 100644 ncutils/power_windows.go diff --git a/ncutils/power_darwin.go b/ncutils/power_darwin.go new file mode 100644 index 00000000..9f18b4fa --- /dev/null +++ b/ncutils/power_darwin.go @@ -0,0 +1,11 @@ +package ncutils + +// RegisterPowerEventHandlers is a no-op on Darwin. It exists so callers can +// register suspend/resume handlers without platform-specific build tags; +// only Windows currently delivers these events. +func RegisterPowerEventHandlers(resumeAutomatic, resumeSuspend func()) error { + return nil +} + +// UnregisterPowerEventHandlers is a no-op on Darwin. +func UnregisterPowerEventHandlers() {} diff --git a/ncutils/power_linux.go b/ncutils/power_linux.go new file mode 100644 index 00000000..ee459047 --- /dev/null +++ b/ncutils/power_linux.go @@ -0,0 +1,11 @@ +package ncutils + +// RegisterPowerEventHandlers is a no-op on Linux. It exists so callers can +// register suspend/resume handlers without platform-specific build tags; +// only Windows currently delivers these events. +func RegisterPowerEventHandlers(resumeAutomatic, resumeSuspend func()) error { + return nil +} + +// UnregisterPowerEventHandlers is a no-op on Linux. +func UnregisterPowerEventHandlers() {} diff --git a/ncutils/power_windows.go b/ncutils/power_windows.go new file mode 100644 index 00000000..a1a06398 --- /dev/null +++ b/ncutils/power_windows.go @@ -0,0 +1,99 @@ +package ncutils + +import ( + "syscall" + "unsafe" + + "github.com/gravitl/netmaker/logger" +) + +// Power event types delivered to the suspend/resume callback. +// See: https://learn.microsoft.com/en-us/windows/win32/power/system-power-status +const ( + // PBT_APMRESUMESUSPEND - the user resumed interaction with the system after the system + // entered a low-power/suspended state due to user activity (e.g. closing a laptop lid + // and reopening it). + PBT_APMRESUMESUSPEND = 0x7 + // PBT_APMRESUMEAUTOMATIC - the system has resumed operation, signaling that the + // system may have resumed automatically (e.g. from hibernate) without user interaction. + PBT_APMRESUMEAUTOMATIC = 0x12 + + deviceNotifyCallback = 2 +) + +// deviceNotifySubscribeParameters mirrors DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS. +type deviceNotifySubscribeParameters struct { + callback uintptr + context uintptr +} + +var ( + modUser32 = syscall.NewLazyDLL("user32.dll") + procRegisterSuspendResumeNotification = modUser32.NewProc("RegisterSuspendResumeNotification") + procUnregisterSuspendResumeNotification = modUser32.NewProc("UnregisterSuspendResumeNotification") + + suspendResumeNotifyHandle uintptr + powerEventCallbackPtr uintptr + + onResumeAutomatic func() + onResumeSuspend func() +) + +// RegisterPowerEventHandlers registers the given functions to run when Windows +// broadcasts PBT_APMRESUMEAUTOMATIC (system resumed, possibly without user +// interaction) and PBT_APMRESUMESUSPEND (user resumed interaction after suspend). +// Either handler may be nil to ignore that event. Calling this again replaces +// any previously registered handlers and re-registers the notification. +func RegisterPowerEventHandlers(resumeAutomatic, resumeSuspend func()) error { + UnregisterPowerEventHandlers() + + onResumeAutomatic = resumeAutomatic + onResumeSuspend = resumeSuspend + + if powerEventCallbackPtr == 0 { + powerEventCallbackPtr = syscall.NewCallback(powerEventCallback) + } + + params := deviceNotifySubscribeParameters{ + callback: powerEventCallbackPtr, + } + + handle, _, err := procRegisterSuspendResumeNotification.Call( + uintptr(unsafe.Pointer(¶ms)), + uintptr(deviceNotifyCallback), + ) + if handle == 0 { + return err + } + suspendResumeNotifyHandle = handle + return nil +} + +// UnregisterPowerEventHandlers unregisters any previously registered power +// event handlers and stops delivery of suspend/resume notifications. +func UnregisterPowerEventHandlers() { + if suspendResumeNotifyHandle != 0 { + procUnregisterSuspendResumeNotification.Call(suspendResumeNotifyHandle) + suspendResumeNotifyHandle = 0 + } + onResumeAutomatic = nil + onResumeSuspend = nil +} + +// powerEventCallback is invoked by Windows on a system thread when a power +// event occurs. It must match PDEVICE_NOTIFY_CALLBACK_ROUTINE's signature. +func powerEventCallback(context, eventType, setting uintptr) uintptr { + switch eventType { + case PBT_APMRESUMEAUTOMATIC: + logger.Log(0, "windows power event: PBT_APMRESUMEAUTOMATIC (system resumed)") + if onResumeAutomatic != nil { + onResumeAutomatic() + } + case PBT_APMRESUMESUSPEND: + logger.Log(0, "windows power event: PBT_APMRESUMESUSPEND (user resumed interaction after suspend)") + if onResumeSuspend != nil { + onResumeSuspend() + } + } + return 0 +} From 042632fb5f6e69f0044cb4d3d557612036186dde Mon Sep 17 00:00:00 2001 From: VishalDalwadi Date: Fri, 31 Jul 2026 14:24:54 +0530 Subject: [PATCH 2/3] wip(go): add callback for suspend; --- ncutils/power_darwin.go | 2 +- ncutils/power_linux.go | 2 +- ncutils/power_windows.go | 28 +++++++++++++++++++++++----- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ncutils/power_darwin.go b/ncutils/power_darwin.go index 9f18b4fa..70ec64f5 100644 --- a/ncutils/power_darwin.go +++ b/ncutils/power_darwin.go @@ -3,7 +3,7 @@ package ncutils // RegisterPowerEventHandlers is a no-op on Darwin. It exists so callers can // register suspend/resume handlers without platform-specific build tags; // only Windows currently delivers these events. -func RegisterPowerEventHandlers(resumeAutomatic, resumeSuspend func()) error { +func RegisterPowerEventHandlers(suspend, resumeAutomatic, resumeSuspend func()) error { return nil } diff --git a/ncutils/power_linux.go b/ncutils/power_linux.go index ee459047..fb48af1f 100644 --- a/ncutils/power_linux.go +++ b/ncutils/power_linux.go @@ -3,7 +3,7 @@ package ncutils // RegisterPowerEventHandlers is a no-op on Linux. It exists so callers can // register suspend/resume handlers without platform-specific build tags; // only Windows currently delivers these events. -func RegisterPowerEventHandlers(resumeAutomatic, resumeSuspend func()) error { +func RegisterPowerEventHandlers(suspend, resumeAutomatic, resumeSuspend func()) error { return nil } diff --git a/ncutils/power_windows.go b/ncutils/power_windows.go index a1a06398..5a0352ad 100644 --- a/ncutils/power_windows.go +++ b/ncutils/power_windows.go @@ -10,6 +10,11 @@ import ( // Power event types delivered to the suspend/resume callback. // See: https://learn.microsoft.com/en-us/windows/win32/power/system-power-status const ( + // PBT_APMSUSPEND - the system is about to suspend. Handlers registered for + // this event must return quickly (Windows gives very little time, on the + // order of a couple seconds, before suspending regardless) - do only cheap, + // local work here, not network I/O. + PBT_APMSUSPEND = 0x4 // PBT_APMRESUMESUSPEND - the user resumed interaction with the system after the system // entered a low-power/suspended state due to user activity (e.g. closing a laptop lid // and reopening it). @@ -35,18 +40,25 @@ var ( suspendResumeNotifyHandle uintptr powerEventCallbackPtr uintptr + onSuspend func() onResumeAutomatic func() onResumeSuspend func() ) // RegisterPowerEventHandlers registers the given functions to run when Windows -// broadcasts PBT_APMRESUMEAUTOMATIC (system resumed, possibly without user -// interaction) and PBT_APMRESUMESUSPEND (user resumed interaction after suspend). -// Either handler may be nil to ignore that event. Calling this again replaces -// any previously registered handlers and re-registers the notification. -func RegisterPowerEventHandlers(resumeAutomatic, resumeSuspend func()) error { +// broadcasts PBT_APMSUSPEND (system about to suspend), PBT_APMRESUMEAUTOMATIC +// (system resumed, possibly without user interaction), and PBT_APMRESUMESUSPEND +// (user resumed interaction after suspend). Any handler may be nil to ignore +// that event. Calling this again replaces any previously registered handlers +// and re-registers the notification. +// +// Note: suspend must return quickly - Windows only allows a couple seconds +// before it suspends regardless, so the suspend handler should do cheap, +// local work only, not network I/O. +func RegisterPowerEventHandlers(suspend, resumeAutomatic, resumeSuspend func()) error { UnregisterPowerEventHandlers() + onSuspend = suspend onResumeAutomatic = resumeAutomatic onResumeSuspend = resumeSuspend @@ -76,6 +88,7 @@ func UnregisterPowerEventHandlers() { procUnregisterSuspendResumeNotification.Call(suspendResumeNotifyHandle) suspendResumeNotifyHandle = 0 } + onSuspend = nil onResumeAutomatic = nil onResumeSuspend = nil } @@ -84,6 +97,11 @@ func UnregisterPowerEventHandlers() { // event occurs. It must match PDEVICE_NOTIFY_CALLBACK_ROUTINE's signature. func powerEventCallback(context, eventType, setting uintptr) uintptr { switch eventType { + case PBT_APMSUSPEND: + logger.Log(0, "windows power event: PBT_APMSUSPEND (system suspending)") + if onSuspend != nil { + onSuspend() + } case PBT_APMRESUMEAUTOMATIC: logger.Log(0, "windows power event: PBT_APMRESUMEAUTOMATIC (system resumed)") if onResumeAutomatic != nil { From 10858316f0a0737b60bc0247665c60d59c564e15 Mon Sep 17 00:00:00 2001 From: VishalDalwadi Date: Mon, 3 Aug 2026 22:30:54 +0530 Subject: [PATCH 3/3] wip(go): add network change callback funcs; --- ncutils/network_darwin.go | 11 ++++++ ncutils/network_linux.go | 11 ++++++ ncutils/network_windows.go | 74 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 ncutils/network_darwin.go create mode 100644 ncutils/network_linux.go create mode 100644 ncutils/network_windows.go diff --git a/ncutils/network_darwin.go b/ncutils/network_darwin.go new file mode 100644 index 00000000..d6c34aff --- /dev/null +++ b/ncutils/network_darwin.go @@ -0,0 +1,11 @@ +package ncutils + +// RegisterNetworkChangeHandler is a no-op on Darwin. It exists so callers can +// register a network-change handler without platform-specific build tags; +// only Windows currently delivers these events. +func RegisterNetworkChangeHandler(onChange func()) error { + return nil +} + +// UnregisterNetworkChangeHandler is a no-op on Darwin. +func UnregisterNetworkChangeHandler() {} diff --git a/ncutils/network_linux.go b/ncutils/network_linux.go new file mode 100644 index 00000000..e99592ad --- /dev/null +++ b/ncutils/network_linux.go @@ -0,0 +1,11 @@ +package ncutils + +// RegisterNetworkChangeHandler is a no-op on Linux. It exists so callers can +// register a network-change handler without platform-specific build tags; +// only Windows currently delivers these events. +func RegisterNetworkChangeHandler(onChange func()) error { + return nil +} + +// UnregisterNetworkChangeHandler is a no-op on Linux. +func UnregisterNetworkChangeHandler() {} diff --git a/ncutils/network_windows.go b/ncutils/network_windows.go new file mode 100644 index 00000000..bc7ef34a --- /dev/null +++ b/ncutils/network_windows.go @@ -0,0 +1,74 @@ +package ncutils + +import ( + "syscall" + "unsafe" + + "github.com/gravitl/netmaker/logger" +) + +const afUnspec = 0 + +var ( + modIphlpapi = syscall.NewLazyDLL("iphlpapi.dll") + procNotifyIpInterfaceChange = modIphlpapi.NewProc("NotifyIpInterfaceChange") + procCancelMibChangeNotify2 = modIphlpapi.NewProc("CancelMibChangeNotify2") + + networkChangeNotifyHandle uintptr + networkChangeCallbackPtr uintptr + + onNetworkChange func() +) + +// RegisterNetworkChangeHandler registers a function to run whenever an IP +// interface changes state (added, removed, or its parameters change - e.g. +// coming up or going down, gaining/losing an address). This fires +// independently of suspend/resume notifications: it catches network changes +// that happen without any sleep/wake cycle at all (Wi-Fi toggled, cable +// unplugged, hotspot bounced), and also covers cases like Modern Standby +// where the network can stay associated across a real sleep and +// suspend/resume notifications alone wouldn't be enough signal. +func RegisterNetworkChangeHandler(onChange func()) error { + UnregisterNetworkChangeHandler() + + onNetworkChange = onChange + + if networkChangeCallbackPtr == 0 { + networkChangeCallbackPtr = syscall.NewCallback(networkChangeCallback) + } + + var handle uintptr + ret, _, _ := procNotifyIpInterfaceChange.Call( + uintptr(afUnspec), + networkChangeCallbackPtr, + 0, + 0, // InitialNotification = FALSE, don't fire once immediately for existing interfaces. + uintptr(unsafe.Pointer(&handle)), + ) + if ret != 0 { + return syscall.Errno(ret) + } + networkChangeNotifyHandle = handle + return nil +} + +// UnregisterNetworkChangeHandler unregisters any previously registered +// network change handler. +func UnregisterNetworkChangeHandler() { + if networkChangeNotifyHandle != 0 { + procCancelMibChangeNotify2.Call(networkChangeNotifyHandle) + networkChangeNotifyHandle = 0 + } + onNetworkChange = nil +} + +// networkChangeCallback is invoked by Windows on a system thread whenever an +// IP interface's state changes. It must match +// PIPINTERFACE_CHANGE_CALLBACK's signature. +func networkChangeCallback(callerContext, row, notificationType uintptr) uintptr { + logger.Log(2, "windows network interface change notification received") + if onNetworkChange != nil { + onNetworkChange() + } + return 0 +}