package notifier import ( "net/netip" "runtime" "sort" "strings" "sync" "github.com/netbirdio/netbird/client/internal/listener" "github.com/netbirdio/netbird/route" ) type Notifier struct { initialRouteRanges []string routeRanges []string listener listener.NetworkChangeListener listenerMux sync.Mutex } func NewNotifier() *Notifier { return &Notifier{} } func (n *Notifier) SetListener(listener listener.NetworkChangeListener) { n.listenerMux.Lock() defer n.listenerMux.Unlock() n.listener = listener } func (n *Notifier) SetInitialClientRoutes(clientRoutes []*route.Route) { nets := make([]string, 0) for _, r := range clientRoutes { nets = append(nets, r.Network.String()) } sort.Strings(nets) n.initialRouteRanges = nets } func (n *Notifier) OnNewRoutes(idMap route.HAMap) { if runtime.GOOS != "android" { return } newNets := make([]string, 0) for _, routes := range idMap { for _, r := range routes { newNets = append(newNets, r.Network.String()) } } sort.Strings(newNets) switch runtime.GOOS { case "android": if !n.hasDiff(n.initialRouteRanges, newNets) { return } default: if !n.hasDiff(n.routeRanges, newNets) { return } } n.routeRanges = newNets n.notify() } func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) { newNets := make([]string, 0) for _, prefix := range prefixes { newNets = append(newNets, prefix.String()) } sort.Strings(newNets) switch runtime.GOOS { case "android": if !n.hasDiff(n.initialRouteRanges, newNets) { return } default: if !n.hasDiff(n.routeRanges, newNets) { return } } n.routeRanges = newNets n.notify() } func (n *Notifier) notify() { n.listenerMux.Lock() defer n.listenerMux.Unlock() if n.listener == nil { return } go func(l listener.NetworkChangeListener) { l.OnNetworkChanged(strings.Join(addIPv6RangeIfNeeded(n.routeRanges), ",")) }(n.listener) } func (n *Notifier) hasDiff(a []string, b []string) bool { if len(a) != len(b) { return true } for i, v := range a { if v != b[i] { return true } } return false } func (n *Notifier) GetInitialRouteRanges() []string { return addIPv6RangeIfNeeded(n.initialRouteRanges) } // addIPv6RangeIfNeeded returns the input ranges with the default IPv6 range when there is an IPv4 default route. func addIPv6RangeIfNeeded(inputRanges []string) []string { ranges := inputRanges for _, r := range inputRanges { // we are intentionally adding the ipv6 default range in case of ipv4 default range // to ensure that all traffic is managed by the tunnel interface on android if r == "0.0.0.0/0" { ranges = append(ranges, "::/0") break } } return ranges }