diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index 87fb7cbc2..af98986b1 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -8,6 +8,7 @@ import ( "net/netip" "net/url" "runtime" + "slices" "sync" "time" @@ -142,6 +143,7 @@ func NewManager(config ManagerConfig) *DefaultManager { func (m *DefaultManager) setupAndroidRoutes(config ManagerConfig) { cr := m.initialClientRoutes(config.InitialRoutes) + routesForComparison := slices.Clone(cr) if config.DNSFeatureFlag { m.fakeIPManager = fakeip.NewManager() @@ -156,7 +158,7 @@ func (m *DefaultManager) setupAndroidRoutes(config ManagerConfig) { cr = append(cr, fakeIPRoute) } - m.notifier.SetInitialClientRoutes(cr, config.DNSFeatureFlag) + m.notifier.SetInitialClientRoutes(cr, routesForComparison, config.DNSFeatureFlag) } func (m *DefaultManager) setupRefCounters(useNoop bool) { diff --git a/client/internal/routemanager/notifier/notifier.go b/client/internal/routemanager/notifier/notifier.go index 6635e1218..b9e8d92d6 100644 --- a/client/internal/routemanager/notifier/notifier.go +++ b/client/internal/routemanager/notifier/notifier.go @@ -3,6 +3,7 @@ package notifier import ( "net/netip" "runtime" + "slices" "sort" "strings" "sync" @@ -12,8 +13,9 @@ import ( ) type Notifier struct { - initialRouteRanges []string - routeRanges []string + initialRoutes []*route.Route + routesForComparison []*route.Route + dnsFeatureFlag bool listener listener.NetworkChangeListener listenerMux sync.Mutex @@ -29,17 +31,10 @@ func (n *Notifier) SetListener(listener listener.NetworkChangeListener) { n.listener = listener } -func (n *Notifier) SetInitialClientRoutes(clientRoutes []*route.Route, dnsFeatureFlag bool) { - nets := make([]string, 0) - for _, r := range clientRoutes { - if r.IsDynamic() && !dnsFeatureFlag { - // this kind of dynamic route is not supported on android - continue - } - nets = append(nets, r.Network.String()) - } - sort.Strings(nets) - n.initialRouteRanges = nets +func (n *Notifier) SetInitialClientRoutes(allRoutes []*route.Route, routesForComparison []*route.Route, dnsFeatureFlag bool) { + n.dnsFeatureFlag = dnsFeatureFlag + n.initialRoutes = allRoutes + n.routesForComparison = routesForComparison } func (n *Notifier) OnNewRoutes(idMap route.HAMap) { @@ -47,22 +42,16 @@ func (n *Notifier) OnNewRoutes(idMap route.HAMap) { return } - var newNets []string + var newRoutes []*route.Route for _, routes := range idMap { - for _, r := range routes { - if r.IsDynamic() { - continue - } - newNets = append(newNets, r.Network.String()) - } + newRoutes = append(newRoutes, routes...) } - sort.Strings(newNets) - if !n.hasDiff(n.initialRouteRanges, newNets) { + if !n.hasRouteDiff(n.routesForComparison, newRoutes) { return } - n.routeRanges = newNets + n.routesForComparison = newRoutes n.notify() } @@ -74,11 +63,12 @@ func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) { } sort.Strings(newNets) - if !n.hasDiff(n.routeRanges, newNets) { + + currentNets := n.routesToStrings(n.routesForComparison) + if slices.Equal(currentNets, newNets) { return } - n.routeRanges = newNets n.notify() } @@ -89,37 +79,67 @@ func (n *Notifier) notify() { return } + routeStrings := n.routesToStrings(n.routesForComparison) + sort.Strings(routeStrings) go func(l listener.NetworkChangeListener) { - l.OnNetworkChanged(strings.Join(addIPv6RangeIfNeeded(n.routeRanges), ",")) + l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(routeStrings, n.routesForComparison), ",")) }(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 +// hasRouteDiff compares two route slices for differences +func (n *Notifier) hasRouteDiff(a []*route.Route, b []*route.Route) bool { + aFiltered := n.filterRoutes(a) + bFiltered := n.filterRoutes(b) + + slices.SortFunc(aFiltered, func(x, y *route.Route) int { + return strings.Compare(x.NetString(), y.NetString()) + }) + slices.SortFunc(bFiltered, func(x, y *route.Route) int { + return strings.Compare(x.NetString(), y.NetString()) + }) + + return !slices.EqualFunc(aFiltered, bFiltered, func(x, y *route.Route) bool { + return x.NetString() == y.NetString() + }) +} + +// filterRoutes filters routes based on DNS feature flag +func (n *Notifier) filterRoutes(routes []*route.Route) []*route.Route { + filtered := make([]*route.Route, 0, len(routes)) + for _, r := range routes { + if r.IsDynamic() && !n.dnsFeatureFlag { + // this kind of dynamic route is not supported on android + continue } + filtered = append(filtered, r) } - return false + return filtered +} + +// routesToStrings converts routes to string slice (caller should sort if needed) +func (n *Notifier) routesToStrings(routes []*route.Route) []string { + filtered := n.filterRoutes(routes) + nets := make([]string, 0, len(filtered)) + for _, r := range filtered { + nets = append(nets, r.NetString()) + } + return nets } func (n *Notifier) GetInitialRouteRanges() []string { - return addIPv6RangeIfNeeded(n.initialRouteRanges) + initialStrings := n.routesToStrings(n.initialRoutes) + sort.Strings(initialStrings) + return n.addIPv6RangeIfNeeded(initialStrings, n.initialRoutes) } // 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 { +func (n *Notifier) addIPv6RangeIfNeeded(inputRanges []string, routes []*route.Route) []string { + for _, r := range routes { // 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 + if r.Network.Addr().Is4() && r.Network.Bits() == 0 { + return append(slices.Clone(inputRanges), "::/0") } } - return ranges + return inputRanges }