From f088b4ae430392224bea54ffb72117d9112b4131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 23 Sep 2025 19:29:50 +0200 Subject: [PATCH 01/23] Export RenewFD function for Android --- client/android/client.go | 16 +++++++++++++++- client/iface/device/device_android.go | 19 ++++++++++++++----- client/iface/device_android.go | 1 + client/iface/iface_create_android.go | 5 +++++ client/internal/engine.go | 11 +++++++++-- client/internal/iface_common.go | 1 + 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/client/android/client.go b/client/android/client.go index d2d0c37f65e..f241b83f987 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -4,6 +4,7 @@ package android import ( "context" + "fmt" "os" "slices" "sync" @@ -17,9 +18,9 @@ import ( "github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/profilemanager" "github.com/netbirdio/netbird/client/internal/stdnet" + "github.com/netbirdio/netbird/client/net" "github.com/netbirdio/netbird/client/system" "github.com/netbirdio/netbird/formatter" - "github.com/netbirdio/netbird/client/net" ) // ConnectionListener export internal Listener for mobile @@ -156,6 +157,19 @@ func (c *Client) Stop() { c.ctxCancel() } +func (c *Client) RenewTun(fd int) error { + if c.connectClient == nil { + return fmt.Errorf("engine not running") + } + + e := c.connectClient.Engine() + if e == nil { + return fmt.Errorf("engine not initialized") + } + + return e.RenewTun(fd) +} + // SetTraceLogLevel configure the logger to trace level func (c *Client) SetTraceLogLevel() { log.SetLevel(log.TraceLevel) diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go index a731684cccd..411f7f7a420 100644 --- a/client/iface/device/device_android.go +++ b/client/iface/device/device_android.go @@ -3,6 +3,7 @@ package device import ( + "fmt" "strings" log "github.com/sirupsen/logrus" @@ -19,11 +20,12 @@ import ( // WGTunDevice ignore the WGTunDevice interface on Android because the creation of the tun device is different on this platform type WGTunDevice struct { - address wgaddr.Address - port int - key string - mtu uint16 - iceBind *bind.ICEBind + address wgaddr.Address + port int + key string + mtu uint16 + iceBind *bind.ICEBind + // todo: review if we can eliminate the TunAdapter tunAdapter TunAdapter disableDNS bool @@ -104,6 +106,13 @@ func (t *WGTunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) { return udpMux, nil } +func (t *WGTunDevice) RenewTun(fd int) error { + if t.device == nil { + return fmt.Errorf("device not initialized") + } + return fmt.Errorf("not implemented yet") +} + func (t *WGTunDevice) UpdateAddr(addr wgaddr.Address) error { // todo implement return nil diff --git a/client/iface/device_android.go b/client/iface/device_android.go index 4649b8b97f6..56214c58e0d 100644 --- a/client/iface/device_android.go +++ b/client/iface/device_android.go @@ -21,4 +21,5 @@ type WGTunDevice interface { FilteredDevice() *device.FilteredDevice Device() *wgdevice.Device GetNet() *netstack.Net + RenewTun(fd int) error } diff --git a/client/iface/iface_create_android.go b/client/iface/iface_create_android.go index 373a9c95a8b..9e3f0b94863 100644 --- a/client/iface/iface_create_android.go +++ b/client/iface/iface_create_android.go @@ -6,6 +6,7 @@ import ( // CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. // Will reuse an existing one. +// todo: review does this function relly necessary ir can we merge it with iOS func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []string) error { w.mu.Lock() defer w.mu.Unlock() @@ -22,3 +23,7 @@ func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []s func (w *WGIface) Create() error { return fmt.Errorf("this function has not implemented on this platform") } + +func (w *WGIface) RenewTun(fd int) error { + return w.tun.RenewTun(fd) +} diff --git a/client/internal/engine.go b/client/internal/engine.go index d4c465efb3e..14a84dfe29d 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -446,8 +446,6 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL) return fmt.Errorf("up wg interface: %w", err) } - - // if inbound conns are blocked there is no need to create the ACL manager if e.firewall != nil && !e.config.BlockInbound { e.acl = acl.NewDefaultManager(e.firewall) @@ -1828,6 +1826,15 @@ func (e *Engine) GetWgAddr() netip.Addr { return e.wgInterface.Address().IP } +func (e *Engine) RenewTun(fd int) error { + // todo review the mutex usage here. We must to be sure we do not modify the e.wgInterface when run this function + if e.wgInterface == nil { + return fmt.Errorf("wireguard interface not initialized") + } + + return e.wgInterface.RenewTun(fd) +} + // updateDNSForwarder start or stop the DNS forwarder based on the domains and the feature flag func (e *Engine) updateDNSForwarder( enabled bool, diff --git a/client/internal/iface_common.go b/client/internal/iface_common.go index 690fdb7cc49..39372091faa 100644 --- a/client/internal/iface_common.go +++ b/client/internal/iface_common.go @@ -20,6 +20,7 @@ import ( type wgIfaceBase interface { Create() error CreateOnAndroid(routeRange []string, ip string, domains []string) error + RenewTun(fd int) error IsUserspaceBind() bool Name() string Address() wgaddr.Address From b2fbdfabeecf9c76af84847ba3e36492317799bc Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 24 Sep 2025 09:53:36 -0300 Subject: [PATCH 02/23] [client] add initial implementation of RenewTun for android device --- client/iface/device/device_android.go | 32 ++++++++++++++++++- .../iface/device/device_netstack_android.go | 4 +++ client/internal/engine.go | 6 ++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go index 411f7f7a420..77e1927f29b 100644 --- a/client/iface/device/device_android.go +++ b/client/iface/device/device_android.go @@ -110,7 +110,37 @@ func (t *WGTunDevice) RenewTun(fd int) error { if t.device == nil { return fmt.Errorf("device not initialized") } - return fmt.Errorf("not implemented yet") + + tunDevice, name, err := tun.CreateUnmonitoredTUNFromFD(fd) + if err != nil { + _ = unix.Close(fd) + log.Errorf("failed to create Android interface: %s", err) + return err + } + + var filteredDevice = newDeviceFilter(tunDevice) + + log.Debugf("attaching to interface %v", name) + var newDevice = device.NewDevice(filteredDevice, t.iceBind, device.NewLogger(wgLogLevel(), "[netbird] ")) + var configurator = configurer.NewUSPConfigurer(newDevice, name, t.iceBind.ActivityRecorder()) + err = configurator.ConfigureInterface(t.key, t.port) + if err != nil { + newDevice.Close() + configurator.Close() + return err + } + + // Closing old device and configurator + t.device.Close() + t.configurer.Close() + + // Assigning values to class members + t.name = name + t.filteredDevice = filteredDevice + t.device = newDevice + t.configurer = configurator + + return nil } func (t *WGTunDevice) UpdateAddr(addr wgaddr.Address) error { diff --git a/client/iface/device/device_netstack_android.go b/client/iface/device/device_netstack_android.go index 45ae8ba7da6..dd214429295 100644 --- a/client/iface/device/device_netstack_android.go +++ b/client/iface/device/device_netstack_android.go @@ -5,3 +5,7 @@ package device func (t *TunNetstackDevice) Create(routes []string, dns string, searchDomains []string) (WGConfigurer, error) { return t.create() } + +func (t *TunNetstackDevice) RenewTun(fd int) error { + return t.RenewTun(fd) +} diff --git a/client/internal/engine.go b/client/internal/engine.go index 14a84dfe29d..b2caaf114e4 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -149,6 +149,8 @@ type Engine struct { // syncMsgMux is used to guarantee sequential Management Service message processing syncMsgMux *sync.Mutex + wgMux *sync.Mutex + config *EngineConfig mobileDep MobileDependency @@ -231,6 +233,7 @@ func NewEngine( relayManager: relayManager, peerStore: peerstore.NewConnStore(), syncMsgMux: &sync.Mutex{}, + wgMux: &sync.Mutex{}, config: config, mobileDep: mobileDep, STUNs: []*stun.URI{}, @@ -1827,6 +1830,9 @@ func (e *Engine) GetWgAddr() netip.Addr { } func (e *Engine) RenewTun(fd int) error { + e.wgMux.Lock() + defer e.wgMux.Unlock() + // todo review the mutex usage here. We must to be sure we do not modify the e.wgInterface when run this function if e.wgInterface == nil { return fmt.Errorf("wireguard interface not initialized") From c58ccc5e669c7d8975f30565d94b0e9b15a1276f Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 26 Sep 2025 13:23:17 -0300 Subject: [PATCH 03/23] [android] add usage of routeSelector when retrieving networks To return information regarding if the network route is selected for a given peer or not --- client/android/client.go | 17 ++++++++++++----- client/android/networks.go | 9 +++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/client/android/client.go b/client/android/client.go index f241b83f987..755083ae17a 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -215,6 +215,12 @@ func (c *Client) Networks() *NetworkArray { return nil } + routeSelector := routeManager.GetRouteSelector() + if routeSelector == nil { + log.Error("could not get route selector") + return nil + } + networkArray := &NetworkArray{ items: make([]Network, 0), } @@ -230,16 +236,17 @@ func (c *Client) Networks() *NetworkArray { netStr = r.Domains.SafeString() } - peer, err := c.recorder.GetPeer(routes[0].Peer) + routePeer, err := c.recorder.GetPeer(routes[0].Peer) if err != nil { log.Errorf("could not get peer info for %s: %v", routes[0].Peer, err) continue } network := Network{ - Name: string(id), - Network: netStr, - Peer: peer.FQDN, - Status: peer.ConnStatus.String(), + Name: string(id), + Network: netStr, + Peer: routePeer.FQDN, + Status: routePeer.ConnStatus.String(), + IsSelected: routeSelector.IsSelected(id), } networkArray.Add(network) } diff --git a/client/android/networks.go b/client/android/networks.go index aa130420baf..5605c8cac56 100644 --- a/client/android/networks.go +++ b/client/android/networks.go @@ -3,10 +3,11 @@ package android type Network struct { - Name string - Network string - Peer string - Status string + Name string + Network string + Peer string + Status string + IsSelected bool } type NetworkArray struct { From 6e57e550bbc7c8a45942085bd5b8e4b32721134c Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 26 Sep 2025 15:08:16 -0300 Subject: [PATCH 04/23] [android] add route commands to toggle routes --- client/android/client.go | 44 ++++++++++++++++++++++- client/android/route_command.go | 63 +++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 client/android/route_command.go diff --git a/client/android/client.go b/client/android/client.go index 755083ae17a..e39e8c068b3 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -140,7 +140,7 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener defer c.ctxCancel() c.ctxCancelLock.Unlock() - // todo do not throw error in case of cancelled context + // todo toggleRoute not throw error in case of cancelled context ctx = internal.CtxInitState(ctx) c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder) return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener) @@ -274,6 +274,48 @@ func (c *Client) RemoveConnectionListener() { c.recorder.RemoveConnectionListener() } +func (c *Client) toggleRoute(command routeCommand) error { + return command.toggleRoute() +} + +func (c *Client) SelectRoute(route string) error { + client := c.connectClient + if client == nil { + return fmt.Errorf("not connected") + } + + engine := client.Engine() + if engine == nil { + return fmt.Errorf("engine is not running") + } + + manager := engine.GetRouteManager() + if manager == nil { + return fmt.Errorf("could not get route manager") + } + + return c.toggleRoute(selectRouteCommand{route: route, manager: manager}) +} + +func (c *Client) DeselectRoute(route string) error { + client := c.connectClient + if client == nil { + return fmt.Errorf("not connected") + } + + engine := client.Engine() + if engine == nil { + return fmt.Errorf("engine is not running") + } + + manager := engine.GetRouteManager() + if manager == nil { + return fmt.Errorf("could not get route manager") + } + + return c.toggleRoute(deselectRouteCommand{route: route, manager: manager}) +} + func exportEnvList(list *EnvList) { if list == nil { return diff --git a/client/android/route_command.go b/client/android/route_command.go new file mode 100644 index 00000000000..fe8403028cc --- /dev/null +++ b/client/android/route_command.go @@ -0,0 +1,63 @@ +package android + +import ( + "fmt" + "github.com/netbirdio/netbird/client/internal/routemanager" + "github.com/netbirdio/netbird/route" + log "github.com/sirupsen/logrus" + "golang.org/x/exp/maps" +) + +func toggleRoute(id string, manager routemanager.Manager, + operationName string, + routeOperation func(routes []route.NetID, allRoutes []route.NetID) error) error { + netID := route.NetID(id) + routes := []route.NetID{netID} + + log.Debugf("%s with id: %s", operationName, id) + + if err := routeOperation(routes, maps.Keys(manager.GetClientRoutesWithNetID())); err != nil { + log.Debugf("error when %s: %s", operationName, err) + return fmt.Errorf("error %s: %w", operationName, err) + } + + manager.TriggerSelection(manager.GetClientRoutes()) + + return nil +} + +type routeCommand interface { + toggleRoute() error +} + +type selectRouteCommand struct { + route string + manager routemanager.Manager +} + +func (s selectRouteCommand) toggleRoute() error { + routeSelector := s.manager.GetRouteSelector() + if routeSelector == nil { + return fmt.Errorf("no route selector available") + } + + routeOperation := func(routes []route.NetID, allRoutes []route.NetID) error { + return routeSelector.SelectRoutes(routes, true, allRoutes) + } + + return toggleRoute(s.route, s.manager, "selecting route", routeOperation) +} + +type deselectRouteCommand struct { + route string + manager routemanager.Manager +} + +func (d deselectRouteCommand) toggleRoute() error { + routeSelector := d.manager.GetRouteSelector() + if routeSelector == nil { + return fmt.Errorf("no route selector available") + } + + return toggleRoute(d.route, d.manager, "deselecting route", routeSelector.DeselectRoutes) +} From 51b664cd9b12d6463129361a57cdf113c06a7b6b Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 26 Sep 2025 20:20:23 -0300 Subject: [PATCH 05/23] [android] add state file path to android client constructor and struct Android has the same limitation as iOS in regard to file creation, so it can't create the state file at the default location returned by ServiceManager's GetStatePath (/var/lib/netbird/state.json) --- client/android/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/android/client.go b/client/android/client.go index e39e8c068b3..8ce0d4bb623 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -63,12 +63,13 @@ type Client struct { deviceName string uiVersion string networkChangeListener listener.NetworkChangeListener + stateFile string connectClient *internal.ConnectClient } // NewClient instantiate a new Client -func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client { +func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener, stateFile string) *Client { execWorkaround(androidSDKVersion) net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket) @@ -81,6 +82,7 @@ func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersi recorder: peer.NewRecorder(""), ctxCancelLock: &sync.Mutex{}, networkChangeListener: networkChangeListener, + stateFile: stateFile, } } From 880ec0132acfdb4ebd7fe82cb1982bd6c8d302c8 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 26 Sep 2025 20:23:46 -0300 Subject: [PATCH 06/23] [client] add android check to use mobile dependency's StateFilePath When setting engine's state manager --- client/internal/engine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/engine.go b/client/internal/engine.go index b2caaf114e4..603f1fd7a7f 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -248,7 +248,7 @@ func NewEngine( sm := profilemanager.NewServiceManager("") path := sm.GetStatePath() - if runtime.GOOS == "ios" { + if runtime.GOOS == "ios" || runtime.GOOS == "android" { if !fileExists(mobileDep.StateFilePath) { err := createFile(mobileDep.StateFilePath) if err != nil { From 9016f5d1c7a91cb82fe84d499de0984e8db879ae Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 26 Sep 2025 20:33:47 -0300 Subject: [PATCH 07/23] [client] pass state file to be set as mobile dependency to RunOnAndroid on ConnectClient --- client/android/client.go | 4 ++-- client/internal/connect.go | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/android/client.go b/client/android/client.go index 8ce0d4bb623..0cb2e211327 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -118,7 +118,7 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead // todo do not throw error in case of cancelled context ctx = internal.CtxInitState(ctx) c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder) - return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener) + return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, c.stateFile) } // RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot). @@ -145,7 +145,7 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener // todo toggleRoute not throw error in case of cancelled context ctx = internal.CtxInitState(ctx) c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder) - return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener) + return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, c.stateFile) } // Stop the internal client and free the resources diff --git a/client/internal/connect.go b/client/internal/connect.go index c9331baf5d8..2453a99ccea 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -25,6 +25,7 @@ import ( "github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/profilemanager" "github.com/netbirdio/netbird/client/internal/stdnet" + nbnet "github.com/netbirdio/netbird/client/net" cProto "github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/client/ssh" "github.com/netbirdio/netbird/client/system" @@ -34,7 +35,6 @@ import ( relayClient "github.com/netbirdio/netbird/shared/relay/client" signal "github.com/netbirdio/netbird/shared/signal/client" "github.com/netbirdio/netbird/util" - nbnet "github.com/netbirdio/netbird/client/net" "github.com/netbirdio/netbird/version" ) @@ -74,6 +74,7 @@ func (c *ConnectClient) RunOnAndroid( networkChangeListener listener.NetworkChangeListener, dnsAddresses []netip.AddrPort, dnsReadyListener dns.ReadyListener, + stateFilePath string, ) error { // in case of non Android os these variables will be nil mobileDependency := MobileDependency{ @@ -82,6 +83,7 @@ func (c *ConnectClient) RunOnAndroid( NetworkChangeListener: networkChangeListener, HostDNSAddresses: dnsAddresses, DnsReadyListener: dnsReadyListener, + StateFilePath: stateFilePath, } return c.run(mobileDependency, nil) } From 53d706ad8e4b137c5bc0905b4b251dd5c3f764d9 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 26 Sep 2025 21:32:08 -0300 Subject: [PATCH 08/23] Revert changed todo message --- client/android/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/android/client.go b/client/android/client.go index 0cb2e211327..5d3320f1bb9 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -142,7 +142,7 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener defer c.ctxCancel() c.ctxCancelLock.Unlock() - // todo toggleRoute not throw error in case of cancelled context + // todo do not throw error in case of cancelled context ctx = internal.CtxInitState(ctx) c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder) return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, c.stateFile) From 41fc5aaacee9b0b57ddc94fdec871c04c6531ba4 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Tue, 30 Sep 2025 20:47:08 -0300 Subject: [PATCH 09/23] [android] Add peer routes to peers and network domains to networks --- client/android/client.go | 9 +++++++++ client/android/network_domains.go | 22 ++++++++++++++++++++++ client/android/networks.go | 5 +++++ client/android/peer_notifier.go | 5 +++++ client/android/peer_routes.go | 18 ++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 client/android/network_domains.go create mode 100644 client/android/peer_routes.go diff --git a/client/android/client.go b/client/android/client.go index 5d3320f1bb9..7019adc2aae 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -5,6 +5,7 @@ package android import ( "context" "fmt" + "golang.org/x/exp/maps" "os" "slices" "sync" @@ -193,6 +194,7 @@ func (c *Client) PeersList() *PeerInfoArray { p.IP, p.FQDN, p.ConnStatus.String(), + PeerRoutes{routes: maps.Keys(p.GetRoutes())}, } peerInfos[n] = pi } @@ -233,6 +235,12 @@ func (c *Client) Networks() *NetworkArray { } r := routes[0] + domains := NetworkDomains{} + + for _, domain := range r.Domains { + domains.Add(domain.SafeString()) + } + netStr := r.Network.String() if r.IsDynamic() { netStr = r.Domains.SafeString() @@ -249,6 +257,7 @@ func (c *Client) Networks() *NetworkArray { Peer: routePeer.FQDN, Status: routePeer.ConnStatus.String(), IsSelected: routeSelector.IsSelected(id), + Domains: domains, } networkArray.Add(network) } diff --git a/client/android/network_domains.go b/client/android/network_domains.go new file mode 100644 index 00000000000..687d3763d29 --- /dev/null +++ b/client/android/network_domains.go @@ -0,0 +1,22 @@ +package android + +import "fmt" + +type NetworkDomains struct { + domains []string +} + +func (n *NetworkDomains) Add(domain string) { + n.domains = append(n.domains, domain) +} + +func (n *NetworkDomains) Get(i int) (string, error) { + if i < 0 || i >= len(n.domains) { + return "", fmt.Errorf("%d is out of range", i) + } + return n.domains[i], nil +} + +func (n *NetworkDomains) Size() int { + return len(n.domains) +} diff --git a/client/android/networks.go b/client/android/networks.go index 5605c8cac56..3c3a2593954 100644 --- a/client/android/networks.go +++ b/client/android/networks.go @@ -8,6 +8,11 @@ type Network struct { Peer string Status string IsSelected bool + Domains NetworkDomains +} + +func (n Network) GetNetworkDomains() *NetworkDomains { + return &n.Domains } type NetworkArray struct { diff --git a/client/android/peer_notifier.go b/client/android/peer_notifier.go index 1f5564c723d..91652fc8047 100644 --- a/client/android/peer_notifier.go +++ b/client/android/peer_notifier.go @@ -5,6 +5,11 @@ type PeerInfo struct { IP string FQDN string ConnStatus string // Todo replace to enum + Routes PeerRoutes +} + +func (p *PeerInfo) GetPeerRoutes() *PeerRoutes { + return &p.Routes } // PeerInfoArray is a wrapper of []PeerInfo diff --git a/client/android/peer_routes.go b/client/android/peer_routes.go new file mode 100644 index 00000000000..c0bea33ef0b --- /dev/null +++ b/client/android/peer_routes.go @@ -0,0 +1,18 @@ +package android + +import "fmt" + +type PeerRoutes struct { + routes []string +} + +func (p *PeerRoutes) Get(i int) (string, error) { + if i < 0 || i >= len(p.routes) { + return "", fmt.Errorf("%d is out of range", i) + } + return p.routes[i], nil +} + +func (p *PeerRoutes) Size() int { + return len(p.routes) +} From 970e3dea8b690963954a6cca23fda77e50c37e44 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 1 Oct 2025 14:23:44 -0300 Subject: [PATCH 10/23] [android] Add resolved IP addresses to network domains --- client/android/client.go | 32 +++++++++++++++++----- client/android/network_domains.go | 44 +++++++++++++++++++++++++++---- client/android/peer_notifier.go | 2 ++ client/android/peer_routes.go | 2 ++ 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/client/android/client.go b/client/android/client.go index 7019adc2aae..77cef14e817 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -5,6 +5,8 @@ package android import ( "context" "fmt" + "github.com/netbirdio/netbird/route" + "github.com/netbirdio/netbird/shared/management/domain" "golang.org/x/exp/maps" "os" "slices" @@ -229,19 +231,17 @@ func (c *Client) Networks() *NetworkArray { items: make([]Network, 0), } + resolvedDomains := c.recorder.GetResolvedDomainsStates() + for id, routes := range routeManager.GetClientRoutesWithNetID() { if len(routes) == 0 { continue } r := routes[0] - domains := NetworkDomains{} - - for _, domain := range r.Domains { - domains.Add(domain.SafeString()) - } - + domains := c.getNetworkDomainsFromRoute(r, resolvedDomains) netStr := r.Network.String() + if r.IsDynamic() { netStr = r.Domains.SafeString() } @@ -327,6 +327,26 @@ func (c *Client) DeselectRoute(route string) error { return c.toggleRoute(deselectRouteCommand{route: route, manager: manager}) } +func (c *Client) getNetworkDomainsFromRoute(route *route.Route, resolvedDomains map[domain.Domain]peer.ResolvedDomainInfo) NetworkDomains { + domains := NetworkDomains{} + + for _, d := range route.Domains { + networkDomain := NetworkDomain{ + Address: d.SafeString(), + } + + if info, exists := resolvedDomains[d]; exists { + for _, prefix := range info.Prefixes { + networkDomain.addResolvedIP(prefix.Addr().String()) + } + } + + domains.Add(networkDomain) + } + + return domains +} + func exportEnvList(list *EnvList) { if list == nil { return diff --git a/client/android/network_domains.go b/client/android/network_domains.go index 687d3763d29..5a5033d73d4 100644 --- a/client/android/network_domains.go +++ b/client/android/network_domains.go @@ -1,20 +1,54 @@ +//go:build android + package android import "fmt" +type ResolvedIPs struct { + resolvedIPs []string +} + +func (r *ResolvedIPs) Add(ipAddress string) { + r.resolvedIPs = append(r.resolvedIPs, ipAddress) +} + +func (r *ResolvedIPs) Get(i int) (string, error) { + if i < 0 || i >= len(r.resolvedIPs) { + return "", fmt.Errorf("%d is out of range", i) + } + return r.resolvedIPs[i], nil +} + +func (r *ResolvedIPs) Size() int { + return len(r.resolvedIPs) +} + +type NetworkDomain struct { + Address string + resolvedIPs ResolvedIPs +} + +func (d *NetworkDomain) addResolvedIP(resolvedIP string) { + d.resolvedIPs.Add(resolvedIP) +} + +func (d *NetworkDomain) GetResolvedIPs() *ResolvedIPs { + return &d.resolvedIPs +} + type NetworkDomains struct { - domains []string + domains []NetworkDomain } -func (n *NetworkDomains) Add(domain string) { +func (n *NetworkDomains) Add(domain NetworkDomain) { n.domains = append(n.domains, domain) } -func (n *NetworkDomains) Get(i int) (string, error) { +func (n *NetworkDomains) Get(i int) (*NetworkDomain, error) { if i < 0 || i >= len(n.domains) { - return "", fmt.Errorf("%d is out of range", i) + return nil, fmt.Errorf("%d is out of range", i) } - return n.domains[i], nil + return &n.domains[i], nil } func (n *NetworkDomains) Size() int { diff --git a/client/android/peer_notifier.go b/client/android/peer_notifier.go index 91652fc8047..b03947da186 100644 --- a/client/android/peer_notifier.go +++ b/client/android/peer_notifier.go @@ -1,3 +1,5 @@ +//go:build android + package android // PeerInfo describe information about the peers. It designed for the UI usage diff --git a/client/android/peer_routes.go b/client/android/peer_routes.go index c0bea33ef0b..bb46d609fbb 100644 --- a/client/android/peer_routes.go +++ b/client/android/peer_routes.go @@ -1,3 +1,5 @@ +//go:build android + package android import "fmt" From eed622db749b6497d6077786f1628cf1673a3007 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Tue, 7 Oct 2025 18:31:18 -0300 Subject: [PATCH 11/23] [android] Add rudimentary implementation of RenewableTUN --- client/iface/device/renewable_tun.go | 130 +++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 client/iface/device/renewable_tun.go diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go new file mode 100644 index 00000000000..aa13efce457 --- /dev/null +++ b/client/iface/device/renewable_tun.go @@ -0,0 +1,130 @@ +package device + +import ( + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/tun" + "os" +) + +type RenewableTUN struct { + devices []tun.Device +} + +func (r *RenewableTUN) File() *os.File { + log.Debug("sending device file.") + return r.peekLast().File() +} + +func (r *RenewableTUN) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { + log.Debug("reading from device.") + n, err = r.peekLast().Read(bufs, sizes, offset) + + if err != nil { + log.Debugf("error reading from device: %v", err) + } + + return n, nil +} + +func (r *RenewableTUN) Write(bufs [][]byte, offset int) (int, error) { + log.Debug("writing to device.") + n, err := r.peekLast().Write(bufs, offset) + + if err != nil { + log.Debugf("error writing to device: %v", err) + } + + return n, nil +} + +func (r *RenewableTUN) MTU() (int, error) { + log.Debug("sending mtu.") + return r.peekLast().MTU() +} + +func (r *RenewableTUN) Name() (string, error) { + log.Debug("sending name.") + return r.peekLast().Name() +} + +func (r *RenewableTUN) Events() <-chan tun.Event { + log.Debug("returning events channel.") + return r.peekLast().Events() +} + +func (r *RenewableTUN) Close() error { + log.Debug("closing.") + + var err error + + for _, device := range r.devices { + err = device.Close() + } + + clear(r.devices) + + return err +} + +func (r *RenewableTUN) BatchSize() int { + log.Debug("returning batch size.") + + return 1 +} + +func (r *RenewableTUN) addDevice(device tun.Device) { + first := r.dequeue() + + if first != nil { + defer func(first tun.Device) { + err := first.Close() + if err != nil { + log.Debug("Error closing first device.") + } + }(first) + } + + r.devices = append(r.devices, device) +} + +func (r *RenewableTUN) peekLast() tun.Device { + //r.mu.Lock() + //defer r.mu.Unlock() + + if len(r.devices) == 0 { + return nil + } + + return r.devices[len(r.devices)-1] +} + +func (r *RenewableTUN) peek() tun.Device { + //r.mu.Lock() + //defer r.mu.Unlock() + if len(r.devices) == 0 { + return nil + } + + return r.devices[0] +} + +func (r *RenewableTUN) dequeue() tun.Device { + //r.mu.Lock() + //defer r.mu.Unlock() + + if len(r.devices) == 0 { + return nil + } + + first := r.devices[0] + r.devices = r.devices[1:] + return first +} + +func NewRenewableTUN() *RenewableTUN { + r := &RenewableTUN{ + devices: make([]tun.Device, 0), + } + + return r +} From 369a5609b71c10df9fbed5e96502c38cd247e52e Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Tue, 7 Oct 2025 20:35:09 -0300 Subject: [PATCH 12/23] [android] Add some nil checks to tun.Device implementations on RenewableTUN --- client/iface/device/renewable_tun.go | 56 ++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index aa13efce457..c7c3d87d313 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -1,6 +1,7 @@ package device import ( + "errors" log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/tun" "os" @@ -12,12 +13,24 @@ type RenewableTUN struct { func (r *RenewableTUN) File() *os.File { log.Debug("sending device file.") - return r.peekLast().File() + + device := r.peekLast() + if device == nil { + return nil + } + + return device.File() } func (r *RenewableTUN) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { log.Debug("reading from device.") - n, err = r.peekLast().Read(bufs, sizes, offset) + + device := r.peekLast() + if device == nil { + return 0, errors.New("no available devices") + } + + n, err = device.Read(bufs, sizes, offset) if err != nil { log.Debugf("error reading from device: %v", err) @@ -28,7 +41,13 @@ func (r *RenewableTUN) Read(bufs [][]byte, sizes []int, offset int) (n int, err func (r *RenewableTUN) Write(bufs [][]byte, offset int) (int, error) { log.Debug("writing to device.") - n, err := r.peekLast().Write(bufs, offset) + + device := r.peekLast() + if device == nil { + return 0, nil + } + + n, err := device.Write(bufs, offset) if err != nil { log.Debugf("error writing to device: %v", err) @@ -39,26 +58,48 @@ func (r *RenewableTUN) Write(bufs [][]byte, offset int) (int, error) { func (r *RenewableTUN) MTU() (int, error) { log.Debug("sending mtu.") - return r.peekLast().MTU() + + device := r.peekLast() + if device == nil { + return 0, nil + } + + return device.MTU() } func (r *RenewableTUN) Name() (string, error) { log.Debug("sending name.") - return r.peekLast().Name() + + device := r.peekLast() + if device == nil { + return "", nil + } + + return device.Name() } func (r *RenewableTUN) Events() <-chan tun.Event { log.Debug("returning events channel.") - return r.peekLast().Events() + + device := r.peekLast() + if device == nil { + return nil + } + + return device.Events() } func (r *RenewableTUN) Close() error { - log.Debug("closing.") + log.Debugf("closing %d devices.", len(r.devices)) var err error for _, device := range r.devices { err = device.Close() + + if err != nil { + log.Debugf("error closing a device: %v", err) + } } clear(r.devices) @@ -75,6 +116,7 @@ func (r *RenewableTUN) BatchSize() int { func (r *RenewableTUN) addDevice(device tun.Device) { first := r.dequeue() + // defers closing the old device after adding the new one if there was any. if first != nil { defer func(first tun.Device) { err := first.Close() From e67cd45b5887c338f6939cd00bc0fb225a258d4f Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Tue, 7 Oct 2025 20:35:47 -0300 Subject: [PATCH 13/23] [android] Add usage of RenewableTUN when renewing tun fd on android --- client/iface/device/device_android.go | 49 ++++++++++----------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go index 77e1927f29b..3f7dcfb76fe 100644 --- a/client/iface/device/device_android.go +++ b/client/iface/device/device_android.go @@ -34,17 +34,19 @@ type WGTunDevice struct { filteredDevice *FilteredDevice udpMux *udpmux.UniversalUDPMuxDefault configurer WGConfigurer + renewableTun *RenewableTUN } func NewTunDevice(address wgaddr.Address, port int, key string, mtu uint16, iceBind *bind.ICEBind, tunAdapter TunAdapter, disableDNS bool) *WGTunDevice { return &WGTunDevice{ - address: address, - port: port, - key: key, - mtu: mtu, - iceBind: iceBind, - tunAdapter: tunAdapter, - disableDNS: disableDNS, + address: address, + port: port, + key: key, + mtu: mtu, + iceBind: iceBind, + tunAdapter: tunAdapter, + disableDNS: disableDNS, + renewableTun: NewRenewableTUN(), } } @@ -67,14 +69,17 @@ func (t *WGTunDevice) Create(routes []string, dns string, searchDomains []string return nil, err } - tunDevice, name, err := tun.CreateUnmonitoredTUNFromFD(fd) + unmonitoredTUN, name, err := tun.CreateUnmonitoredTUNFromFD(fd) if err != nil { _ = unix.Close(fd) log.Errorf("failed to create Android interface: %s", err) return nil, err } + + t.renewableTun.addDevice(unmonitoredTUN) + t.name = name - t.filteredDevice = newDeviceFilter(tunDevice) + t.filteredDevice = newDeviceFilter(t.renewableTun) log.Debugf("attaching to interface %v", name) t.device = device.NewDevice(t.filteredDevice, t.iceBind, device.NewLogger(wgLogLevel(), "[netbird] ")) @@ -111,34 +116,14 @@ func (t *WGTunDevice) RenewTun(fd int) error { return fmt.Errorf("device not initialized") } - tunDevice, name, err := tun.CreateUnmonitoredTUNFromFD(fd) + unmonitoredTUN, _, err := tun.CreateUnmonitoredTUNFromFD(fd) if err != nil { _ = unix.Close(fd) - log.Errorf("failed to create Android interface: %s", err) + log.Errorf("failed to renew Android interface: %s", err) return err } - var filteredDevice = newDeviceFilter(tunDevice) - - log.Debugf("attaching to interface %v", name) - var newDevice = device.NewDevice(filteredDevice, t.iceBind, device.NewLogger(wgLogLevel(), "[netbird] ")) - var configurator = configurer.NewUSPConfigurer(newDevice, name, t.iceBind.ActivityRecorder()) - err = configurator.ConfigureInterface(t.key, t.port) - if err != nil { - newDevice.Close() - configurator.Close() - return err - } - - // Closing old device and configurator - t.device.Close() - t.configurer.Close() - - // Assigning values to class members - t.name = name - t.filteredDevice = filteredDevice - t.device = newDevice - t.configurer = configurator + t.renewableTun.addDevice(unmonitoredTUN) return nil } From 040ed997d272753af3378b3ee3662167b739a534 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 8 Oct 2025 11:54:20 -0300 Subject: [PATCH 14/23] [android] Use mutex when peeking, dequeueing and adding to devices list --- client/iface/device/renewable_tun.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index c7c3d87d313..b48696ad148 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -5,10 +5,12 @@ import ( log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/tun" "os" + "sync" ) type RenewableTUN struct { devices []tun.Device + mu sync.Mutex } func (r *RenewableTUN) File() *os.File { @@ -90,6 +92,9 @@ func (r *RenewableTUN) Events() <-chan tun.Event { } func (r *RenewableTUN) Close() error { + r.mu.Lock() + defer r.mu.Unlock() + log.Debugf("closing %d devices.", len(r.devices)) var err error @@ -126,12 +131,14 @@ func (r *RenewableTUN) addDevice(device tun.Device) { }(first) } + r.mu.Lock() + defer r.mu.Unlock() r.devices = append(r.devices, device) } func (r *RenewableTUN) peekLast() tun.Device { - //r.mu.Lock() - //defer r.mu.Unlock() + r.mu.Lock() + defer r.mu.Unlock() if len(r.devices) == 0 { return nil @@ -140,19 +147,9 @@ func (r *RenewableTUN) peekLast() tun.Device { return r.devices[len(r.devices)-1] } -func (r *RenewableTUN) peek() tun.Device { - //r.mu.Lock() - //defer r.mu.Unlock() - if len(r.devices) == 0 { - return nil - } - - return r.devices[0] -} - func (r *RenewableTUN) dequeue() tun.Device { - //r.mu.Lock() - //defer r.mu.Unlock() + r.mu.Lock() + defer r.mu.Unlock() if len(r.devices) == 0 { return nil @@ -166,6 +163,7 @@ func (r *RenewableTUN) dequeue() tun.Device { func NewRenewableTUN() *RenewableTUN { r := &RenewableTUN{ devices: make([]tun.Device, 0), + mu: sync.Mutex{}, } return r From fb864e56c5a458e10472fc56e8f56dd482bf87ff Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 8 Oct 2025 13:11:19 -0300 Subject: [PATCH 15/23] [test] Add missing RenewFd mock to MockWGIface used in engine_test --- client/internal/engine_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 2f1098100ac..cde0415e4f1 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -103,6 +103,11 @@ type MockWGIface struct { GetProxyFunc func() wgproxy.Proxy GetNetFunc func() *netstack.Net LastActivitiesFunc func() map[string]monotime.Time + RenewFd func(int) error +} + +func (m *MockWGIface) RenewTun(_ int) error { + return nil } func (m *MockWGIface) RemoveEndpointAddress(_ string) error { From 971eea3cda2078806e7cae74adf4da871a50d667 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 8 Oct 2025 14:18:55 -0300 Subject: [PATCH 16/23] [client] Add missing RenewTun implementation Added to iface_create.go and iface_create_darwin.go Since this is only used in android's context, it throws an error when those implementations are called --- client/iface/iface_create.go | 4 ++++ client/iface/iface_create_darwin.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/client/iface/iface_create.go b/client/iface/iface_create.go index 5e17c6d4149..13ae9393c5f 100644 --- a/client/iface/iface_create.go +++ b/client/iface/iface_create.go @@ -24,3 +24,7 @@ func (w *WGIface) Create() error { func (w *WGIface) CreateOnAndroid([]string, string, []string) error { return fmt.Errorf("this function has not implemented on non mobile") } + +func (w *WGIface) RenewTun(fd int) error { + return fmt.Errorf("this function has not been implemented on non-android") +} diff --git a/client/iface/iface_create_darwin.go b/client/iface/iface_create_darwin.go index 1d91bce54bd..0b7cd36efc9 100644 --- a/client/iface/iface_create_darwin.go +++ b/client/iface/iface_create_darwin.go @@ -39,3 +39,7 @@ func (w *WGIface) Create() error { func (w *WGIface) CreateOnAndroid([]string, string, []string) error { return fmt.Errorf("this function has not implemented on this platform") } + +func (w *WGIface) RenewTun(fd int) error { + return fmt.Errorf("this function has not been implemented on this platform") +} From 546d022a044a857ab7ab41f1ffb92cef83829b01 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 8 Oct 2025 14:19:45 -0300 Subject: [PATCH 17/23] [test] remove unused definition from MockWGIface struct --- client/internal/engine_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index cde0415e4f1..72418d674d0 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -103,7 +103,6 @@ type MockWGIface struct { GetProxyFunc func() wgproxy.Proxy GetNetFunc func() *netstack.Net LastActivitiesFunc func() map[string]monotime.Time - RenewFd func(int) error } func (m *MockWGIface) RenewTun(_ int) error { From 039c9405d42f2fce530f833810c72dafa19e721f Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 8 Oct 2025 14:37:51 -0300 Subject: [PATCH 18/23] [client] add go:build android to new android files --- client/android/route_command.go | 2 ++ client/iface/device/renewable_tun.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/client/android/route_command.go b/client/android/route_command.go index fe8403028cc..895d7a120c1 100644 --- a/client/android/route_command.go +++ b/client/android/route_command.go @@ -1,3 +1,5 @@ +//go:build android + package android import ( diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index b48696ad148..30692561eaf 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -1,3 +1,5 @@ +//go:build android + package device import ( From 90a78467dcc81b61d4776059aa264d891a9c629f Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Wed, 8 Oct 2025 22:58:30 -0300 Subject: [PATCH 19/23] [client] sort imports, fix typo --- client/android/client.go | 7 ++++--- client/iface/device/renewable_tun.go | 5 +++-- client/iface/iface_create_android.go | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/client/android/client.go b/client/android/client.go index 77cef14e817..31fa167bab7 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -5,13 +5,12 @@ package android import ( "context" "fmt" - "github.com/netbirdio/netbird/route" - "github.com/netbirdio/netbird/shared/management/domain" - "golang.org/x/exp/maps" "os" "slices" "sync" + "golang.org/x/exp/maps" + log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/client/iface/device" @@ -24,6 +23,8 @@ import ( "github.com/netbirdio/netbird/client/net" "github.com/netbirdio/netbird/client/system" "github.com/netbirdio/netbird/formatter" + "github.com/netbirdio/netbird/route" + "github.com/netbirdio/netbird/shared/management/domain" ) // ConnectionListener export internal Listener for mobile diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index 30692561eaf..4a1497f2f2b 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -4,10 +4,11 @@ package device import ( "errors" - log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/tun" "os" "sync" + + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/tun" ) type RenewableTUN struct { diff --git a/client/iface/iface_create_android.go b/client/iface/iface_create_android.go index 9e3f0b94863..72186bff924 100644 --- a/client/iface/iface_create_android.go +++ b/client/iface/iface_create_android.go @@ -6,7 +6,7 @@ import ( // CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. // Will reuse an existing one. -// todo: review does this function relly necessary ir can we merge it with iOS +// todo: review does this function really necessary ir can we merge it with iOS func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []string) error { w.mu.Lock() defer w.mu.Unlock() From 4ff93dabd0d55e318d7f9e4a9c4c6a82f988252e Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Thu, 9 Oct 2025 00:10:41 -0300 Subject: [PATCH 20/23] [android] add PlatformFiles interface to group config and state file paths This was done to lower the amount of parameters passed to android/client's NewClient function --- client/android/client.go | 6 +++--- client/android/platform_files.go | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 client/android/platform_files.go diff --git a/client/android/client.go b/client/android/client.go index 31fa167bab7..fe11b2e9a38 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -73,12 +73,12 @@ type Client struct { } // NewClient instantiate a new Client -func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener, stateFile string) *Client { +func NewClient(platformFiles PlatformFiles, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client { execWorkaround(androidSDKVersion) net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket) return &Client{ - cfgFile: cfgFile, + cfgFile: platformFiles.ConfigurationFilePath(), deviceName: deviceName, uiVersion: uiVersion, tunAdapter: tunAdapter, @@ -86,7 +86,7 @@ func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersi recorder: peer.NewRecorder(""), ctxCancelLock: &sync.Mutex{}, networkChangeListener: networkChangeListener, - stateFile: stateFile, + stateFile: platformFiles.StateFilePath(), } } diff --git a/client/android/platform_files.go b/client/android/platform_files.go new file mode 100644 index 00000000000..f0c36975086 --- /dev/null +++ b/client/android/platform_files.go @@ -0,0 +1,10 @@ +//go:build android + +package android + +// PlatformFiles groups paths to files used internally by the engine that can't be created/modified +// at their default locations due to android OS restrictions. +type PlatformFiles interface { + ConfigurationFilePath() string + StateFilePath() string +} From e1d4d24c21bbd7dd38b14f3dc454f63c639347db Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 17 Oct 2025 18:25:18 -0300 Subject: [PATCH 21/23] [client] Move constructor to under type definition --- client/iface/device/renewable_tun.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index 4a1497f2f2b..35276bda236 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -16,6 +16,15 @@ type RenewableTUN struct { mu sync.Mutex } +func NewRenewableTUN() *RenewableTUN { + r := &RenewableTUN{ + devices: make([]tun.Device, 0), + mu: sync.Mutex{}, + } + + return r +} + func (r *RenewableTUN) File() *os.File { log.Debug("sending device file.") @@ -162,12 +171,3 @@ func (r *RenewableTUN) dequeue() tun.Device { r.devices = r.devices[1:] return first } - -func NewRenewableTUN() *RenewableTUN { - r := &RenewableTUN{ - devices: make([]tun.Device, 0), - mu: sync.Mutex{}, - } - - return r -} From 23c42773d14093e7d7cd75b1763b7cb6dd69bb7a Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 17 Oct 2025 18:27:34 -0300 Subject: [PATCH 22/23] [client] Export AddDevice function in RenewableTUN --- client/iface/device/device_android.go | 4 ++-- client/iface/device/renewable_tun.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go index ea953301fd6..198343fbd3b 100644 --- a/client/iface/device/device_android.go +++ b/client/iface/device/device_android.go @@ -76,7 +76,7 @@ func (t *WGTunDevice) Create(routes []string, dns string, searchDomains []string return nil, err } - t.renewableTun.addDevice(unmonitoredTUN) + t.renewableTun.AddDevice(unmonitoredTUN) t.name = name t.filteredDevice = newDeviceFilter(t.renewableTun) @@ -123,7 +123,7 @@ func (t *WGTunDevice) RenewTun(fd int) error { return err } - t.renewableTun.addDevice(unmonitoredTUN) + t.renewableTun.AddDevice(unmonitoredTUN) return nil } diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index 35276bda236..1c194ce182d 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -130,7 +130,7 @@ func (r *RenewableTUN) BatchSize() int { return 1 } -func (r *RenewableTUN) addDevice(device tun.Device) { +func (r *RenewableTUN) AddDevice(device tun.Device) { first := r.dequeue() // defers closing the old device after adding the new one if there was any. From 6dc4d57831acfaace7b102d96040c6420df23d68 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Fri, 17 Oct 2025 21:16:49 -0300 Subject: [PATCH 23/23] [client] Wrap RenewableTUN's device with closeAwareDevice closeAwareDevice adds a bool indicating whether its wrapped tun.Device's Close method was called. This was done so that when the engine reads or writes from a device, it won't receive an error if the device has already been closed in order to not call RenewableTUN Close method (which will close all devices) --- client/iface/device/renewable_tun.go | 81 ++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/client/iface/device/renewable_tun.go b/client/iface/device/renewable_tun.go index 1c194ce182d..1e4cddd1347 100644 --- a/client/iface/device/renewable_tun.go +++ b/client/iface/device/renewable_tun.go @@ -11,14 +11,51 @@ import ( "golang.zx2c4.com/wireguard/tun" ) +// closeAwareDevice wraps a tun.Device along with a flag +// indicating whether its Close method was called. +type closeAwareDevice struct { + isClosed bool + tun.Device +} + +func newClosableDevice(tunDevice tun.Device) *closeAwareDevice { + return &closeAwareDevice{ + Device: tunDevice, + isClosed: false, + } +} + +// Close calls the underlying Device's Close method +// after setting isClosed to true. +func (c *closeAwareDevice) Close() (err error) { + fd := c.Device.File().Fd() + + defer func() { + if err == nil { + log.Debugf("device %v is now closed", fd) + } else { + log.Debugf("error attempting to close device %v: %v", fd, err) + } + }() + + c.isClosed = true + err = c.Device.Close() + + return err +} + +func (c *closeAwareDevice) IsClosed() bool { + return c.isClosed +} + type RenewableTUN struct { - devices []tun.Device + devices []*closeAwareDevice mu sync.Mutex } func NewRenewableTUN() *RenewableTUN { r := &RenewableTUN{ - devices: make([]tun.Device, 0), + devices: make([]*closeAwareDevice, 0), mu: sync.Mutex{}, } @@ -26,7 +63,7 @@ func NewRenewableTUN() *RenewableTUN { } func (r *RenewableTUN) File() *os.File { - log.Debug("sending device file.") + log.Debug("sending device file") device := r.peekLast() if device == nil { @@ -37,7 +74,7 @@ func (r *RenewableTUN) File() *os.File { } func (r *RenewableTUN) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { - log.Debug("reading from device.") + log.Debug("reading from device") device := r.peekLast() if device == nil { @@ -48,13 +85,19 @@ func (r *RenewableTUN) Read(bufs [][]byte, sizes []int, offset int) (n int, err if err != nil { log.Debugf("error reading from device: %v", err) + + if device.IsClosed() { + err = nil + } + } else { + log.Debugf("read %v bytes from device %v", n, device.File().Fd()) } - return n, nil + return n, err } func (r *RenewableTUN) Write(bufs [][]byte, offset int) (int, error) { - log.Debug("writing to device.") + log.Debug("writing to device") device := r.peekLast() if device == nil { @@ -65,13 +108,17 @@ func (r *RenewableTUN) Write(bufs [][]byte, offset int) (int, error) { if err != nil { log.Debugf("error writing to device: %v", err) + + if device.IsClosed() { + err = nil + } } - return n, nil + return n, err } func (r *RenewableTUN) MTU() (int, error) { - log.Debug("sending mtu.") + log.Debug("sending mtu") device := r.peekLast() if device == nil { @@ -82,7 +129,7 @@ func (r *RenewableTUN) MTU() (int, error) { } func (r *RenewableTUN) Name() (string, error) { - log.Debug("sending name.") + log.Debug("sending name") device := r.peekLast() if device == nil { @@ -93,7 +140,7 @@ func (r *RenewableTUN) Name() (string, error) { } func (r *RenewableTUN) Events() <-chan tun.Event { - log.Debug("returning events channel.") + log.Debug("returning events channel") device := r.peekLast() if device == nil { @@ -107,7 +154,7 @@ func (r *RenewableTUN) Close() error { r.mu.Lock() defer r.mu.Unlock() - log.Debugf("closing %d devices.", len(r.devices)) + log.Debugf("closing %d devices", len(r.devices)) var err error @@ -125,7 +172,7 @@ func (r *RenewableTUN) Close() error { } func (r *RenewableTUN) BatchSize() int { - log.Debug("returning batch size.") + log.Debug("returning batch size") return 1 } @@ -135,20 +182,20 @@ func (r *RenewableTUN) AddDevice(device tun.Device) { // defers closing the old device after adding the new one if there was any. if first != nil { - defer func(first tun.Device) { + defer func(first *closeAwareDevice) { err := first.Close() if err != nil { - log.Debug("Error closing first device.") + log.Debugf("error closing first device: %v", err) } }(first) } r.mu.Lock() defer r.mu.Unlock() - r.devices = append(r.devices, device) + r.devices = append(r.devices, newClosableDevice(device)) } -func (r *RenewableTUN) peekLast() tun.Device { +func (r *RenewableTUN) peekLast() *closeAwareDevice { r.mu.Lock() defer r.mu.Unlock() @@ -159,7 +206,7 @@ func (r *RenewableTUN) peekLast() tun.Device { return r.devices[len(r.devices)-1] } -func (r *RenewableTUN) dequeue() tun.Device { +func (r *RenewableTUN) dequeue() *closeAwareDevice { r.mu.Lock() defer r.mu.Unlock()