@@ -210,6 +210,10 @@ type Engine struct {
210210 // checks are the client-applied posture checks that need to be evaluated on the client
211211 checks []* mgmProto.Checks
212212
213+ // lastNetworkAddresses tracks reported addresses for change detection
214+ lastNetworkAddresses []system.NetworkAddress
215+ lastNetworkAddressSync time.Time
216+
213217 relayManager * relayClient.Manager
214218 stateManager * statemanager.Manager
215219 srWatcher * guard.SRWatcher
@@ -560,6 +564,10 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
560564 e .receiveManagementEvents ()
561565 e .receiveJobEvents ()
562566
567+ // watch for network address changes (WiFi ↔ mobile) for posture checks
568+ e .shutdownWg .Add (1 )
569+ go e .startNetworkAddressWatcher ()
570+
563571 // starting network monitor at the very last to avoid disruptions
564572 e .startNetworkMonitor ()
565573
@@ -884,6 +892,10 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
884892 return err
885893 }
886894
895+ // Fallback: detect network address changes during periodic sync in case
896+ // platform-specific callbacks (e.g., Android NetworkCallback) were missed.
897+ e .resyncMetaIfNetworkChanged ()
898+
887899 nm := update .GetNetworkMap ()
888900 if nm == nil {
889901 return nil
@@ -1003,6 +1015,99 @@ func (e *Engine) updateChecksIfNew(checks []*mgmProto.Checks) error {
10031015 return nil
10041016}
10051017
1018+ // ResyncNetworkAddresses can be called externally (e.g., from Android
1019+ // network change callbacks) to immediately re-sync NetworkAddresses.
1020+ func (e * Engine ) ResyncNetworkAddresses () {
1021+ e .syncMsgMux .Lock ()
1022+ defer e .syncMsgMux .Unlock ()
1023+ e .resyncMetaIfNetworkChanged ()
1024+ }
1025+
1026+ // startNetworkAddressWatcher polls for network address changes every 10s.
1027+ // This catches cases where platform callbacks are missed or delayed,
1028+ // ensuring posture checks always evaluate the current network state.
1029+ func (e * Engine ) startNetworkAddressWatcher () {
1030+ defer e .shutdownWg .Done ()
1031+ ticker := time .NewTicker (10 * time .Second )
1032+ defer ticker .Stop ()
1033+
1034+ for {
1035+ select {
1036+ case <- e .ctx .Done ():
1037+ return
1038+ case <- ticker .C :
1039+ e .syncMsgMux .Lock ()
1040+ e .resyncMetaIfNetworkChanged ()
1041+ e .syncMsgMux .Unlock ()
1042+ }
1043+ }
1044+ }
1045+
1046+ // resyncMetaIfNetworkChanged detects changes in local network addresses
1047+ // (e.g., WiFi reconnect on mobile) and re-syncs meta with the management
1048+ // server so that posture checks evaluate the current network state.
1049+ func (e * Engine ) resyncMetaIfNetworkChanged () {
1050+ // Debounce: don't re-sync more than once per 30 seconds to avoid
1051+ // flapping during VPN tunnel setup when interfaces are in flux.
1052+ if time .Since (e .lastNetworkAddressSync ) < 30 * time .Second {
1053+ return
1054+ }
1055+
1056+ info := system .GetInfo (e .ctx )
1057+ if info == nil {
1058+ return
1059+ }
1060+
1061+ current := info .NetworkAddresses
1062+ if networkAddressesEqual (e .lastNetworkAddresses , current ) {
1063+ return
1064+ }
1065+
1066+ log .Infof ("network addresses changed (%d -> %d addrs), re-syncing meta with management server" ,
1067+ len (e .lastNetworkAddresses ), len (current ))
1068+ e .lastNetworkAddresses = current
1069+ e .lastNetworkAddressSync = time .Now ()
1070+
1071+ info .SetFlags (
1072+ e .config .RosenpassEnabled ,
1073+ e .config .RosenpassPermissive ,
1074+ & e .config .ServerSSHAllowed ,
1075+ e .config .DisableClientRoutes ,
1076+ e .config .DisableServerRoutes ,
1077+ e .config .DisableDNS ,
1078+ e .config .DisableFirewall ,
1079+ e .config .BlockLANAccess ,
1080+ e .config .BlockInbound ,
1081+ e .config .LazyConnectionEnabled ,
1082+ e .config .EnableSSHRoot ,
1083+ e .config .EnableSSHSFTP ,
1084+ e .config .EnableSSHLocalPortForwarding ,
1085+ e .config .EnableSSHRemotePortForwarding ,
1086+ e .config .DisableSSHAuth ,
1087+ )
1088+
1089+ if err := e .mgmClient .SyncMeta (info ); err != nil {
1090+ log .Warnf ("failed to re-sync meta after network change: %v" , err )
1091+ }
1092+ }
1093+
1094+ func networkAddressesEqual (a , b []system.NetworkAddress ) bool {
1095+ if len (a ) != len (b ) {
1096+ return false
1097+ }
1098+ // Sort-unabhängiger Vergleich: prüfe ob alle IPs aus a in b vorkommen
1099+ bSet := make (map [string ]struct {}, len (b ))
1100+ for _ , addr := range b {
1101+ bSet [addr .NetIP .String ()] = struct {}{}
1102+ }
1103+ for _ , addr := range a {
1104+ if _ , ok := bSet [addr .NetIP .String ()]; ! ok {
1105+ return false
1106+ }
1107+ }
1108+ return true
1109+ }
1110+
10061111func (e * Engine ) updateConfig (conf * mgmProto.PeerConfig ) error {
10071112 if e .wgInterface == nil {
10081113 return errors .New ("wireguard interface is not initialized" )
0 commit comments