Skip to content
79 changes: 60 additions & 19 deletions client/android/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,41 +238,82 @@ func (c *Client) Networks() *NetworkArray {
return nil
}

routesMap := routeManager.GetClientRoutesWithNetID()
v6Merged := route.V6ExitMergeSet(routesMap)
resolvedDomains := c.recorder.GetResolvedDomainsStates()

networkArray := &NetworkArray{
items: make([]Network, 0),
}

resolvedDomains := c.recorder.GetResolvedDomainsStates()

for id, routes := range routeManager.GetClientRoutesWithNetID() {
for id, routes := range routesMap {
if len(routes) == 0 {
continue
}
if _, skip := v6Merged[id]; skip {
continue
}

network := c.buildNetwork(id, routes, routeSelector.IsSelected(id), resolvedDomains, v6Merged)
if network == nil {
continue
}
networkArray.Add(*network)
}
return networkArray
}

r := routes[0]
domains := c.getNetworkDomainsFromRoute(r, resolvedDomains)
netStr := r.Network.String()
func (c *Client) buildNetwork(id route.NetID, routes []*route.Route, selected bool, resolvedDomains map[domain.Domain]peer.ResolvedDomainInfo, v6Merged map[route.NetID]struct{}) *Network {
r := routes[0]
netStr := r.Network.String()
if r.IsDynamic() {
netStr = r.Domains.SafeString()
}

if r.IsDynamic() {
netStr = r.Domains.SafeString()
routePeer, err := c.findBestRoutePeer(routes)
if err != nil {
log.Errorf("could not get peer info for route %s: %v", id, err)
return nil
}

network := &Network{
Name: string(id),
Network: netStr,
Peer: routePeer.FQDN,
Status: routePeer.ConnStatus.String(),
IsSelected: selected,
Domains: c.getNetworkDomainsFromRoute(r, resolvedDomains),
}

if route.IsV4DefaultRoute(r.Network) && route.HasV6ExitPair(id, v6Merged) {
network.Network = "0.0.0.0/0, ::/0"
}

return network
}

// findBestRoutePeer returns the peer actively routing traffic for the given
// HA route group. Falls back to the first connected peer, then the first peer.
func (c *Client) findBestRoutePeer(routes []*route.Route) (peer.State, error) {
netStr := routes[0].Network.String()

fullStatus := c.recorder.GetFullStatus()
for _, p := range fullStatus.Peers {
if _, ok := p.GetRoutes()[netStr]; ok {
return p, nil
}
}

routePeer, err := c.recorder.GetPeer(routes[0].Peer)
for _, r := range routes {
p, err := c.recorder.GetPeer(r.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: routePeer.FQDN,
Status: routePeer.ConnStatus.String(),
IsSelected: routeSelector.IsSelected(id),
Domains: domains,
if p.ConnStatus == peer.StatusConnected {
return p, nil
}
networkArray.Add(network)
}
return networkArray
return c.recorder.GetPeer(routes[0].Peer)
}

// OnUpdatedHostDNS update the DNS servers addresses for root zones
Expand Down
7 changes: 5 additions & 2 deletions client/android/route_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ func executeRouteToggle(id string, manager routemanager.Manager,
netID := route.NetID(id)
routes := []route.NetID{netID}

log.Debugf("%s with id: %s", operationName, id)
routesMap := manager.GetClientRoutesWithNetID()
routes = route.ExpandV6ExitPairs(routes, routesMap)

if err := routeOperation(routes, maps.Keys(manager.GetClientRoutesWithNetID())); err != nil {
log.Debugf("%s with ids: %v", operationName, routes)

if err := routeOperation(routes, maps.Keys(routesMap)); err != nil {
log.Debugf("error when %s: %s", operationName, err)
return fmt.Errorf("error %s: %w", operationName, err)
}
Expand Down
6 changes: 5 additions & 1 deletion client/internal/peer/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,11 @@ func (d *Status) notifyPeerListChanged() {
}

func (d *Status) notifyAddressChanged() {
d.notifier.localAddressChanged(d.localPeer.FQDN, d.localPeer.IP)
addr := d.localPeer.IP
if d.localPeer.IPv6 != "" {
addr = addr + "\n" + d.localPeer.IPv6
}
d.notifier.localAddressChanged(d.localPeer.FQDN, addr)
}

func (d *Status) numOfPeers() int {
Expand Down
74 changes: 48 additions & 26 deletions client/ios/NetBirdSDK/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ type CustomLogger interface {
}

type selectRoute struct {
NetID string
Network netip.Prefix
Domains domain.List
Selected bool
NetID string
Network netip.Prefix
Domains domain.List
Selected bool
extraNetworks []netip.Prefix
}

func init() {
Expand Down Expand Up @@ -363,48 +364,60 @@ func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
}

routeManager := engine.GetRouteManager()
routesMap := routeManager.GetClientRoutesWithNetID()
if routeManager == nil {
return nil, fmt.Errorf("could not get route manager")
}
routesMap := routeManager.GetClientRoutesWithNetID()
routeSelector := routeManager.GetRouteSelector()
if routeSelector == nil {
return nil, fmt.Errorf("could not get route selector")
}

v6ExitMerged := route.V6ExitMergeSet(routesMap)
routes := buildSelectRoutes(routesMap, routeSelector.IsSelected, v6ExitMerged)
resolvedDomains := c.recorder.GetResolvedDomainsStates()

return prepareRouteSelectionDetails(routes, resolvedDomains), nil
}

func buildSelectRoutes(routesMap map[route.NetID][]*route.Route, isSelected func(route.NetID) bool, v6Merged map[route.NetID]struct{}) []*selectRoute {
var routes []*selectRoute
for id, rt := range routesMap {
if len(rt) == 0 {
continue
}
route := &selectRoute{
if _, ok := v6Merged[id]; ok {
continue
}

r := &selectRoute{
NetID: string(id),
Network: rt[0].Network,
Domains: rt[0].Domains,
Selected: routeSelector.IsSelected(id),
Selected: isSelected(id),
}
routes = append(routes, route)

v6ID := route.NetID(string(id) + route.V6ExitSuffix)
if _, ok := v6Merged[v6ID]; ok {
r.extraNetworks = []netip.Prefix{routesMap[v6ID][0].Network}
}

routes = append(routes, r)
}

sort.Slice(routes, func(i, j int) bool {
iPrefix := routes[i].Network.Bits()
jPrefix := routes[j].Network.Bits()

if iPrefix == jPrefix {
iAddr := routes[i].Network.Addr()
jAddr := routes[j].Network.Addr()
if iAddr == jAddr {
return routes[i].NetID < routes[j].NetID
}
return iAddr.String() < jAddr.String()
iBits, jBits := routes[i].Network.Bits(), routes[j].Network.Bits()
if iBits != jBits {
return iBits < jBits
}
return iPrefix < jPrefix
iAddr, jAddr := routes[i].Network.Addr(), routes[j].Network.Addr()
if iAddr != jAddr {
return iAddr.Less(jAddr)
}
return routes[i].NetID < routes[j].NetID
})

resolvedDomains := c.recorder.GetResolvedDomainsStates()

return prepareRouteSelectionDetails(routes, resolvedDomains), nil

return routes
}

func prepareRouteSelectionDetails(routes []*selectRoute, resolvedDomains map[domain.Domain]peer.ResolvedDomainInfo) *RoutesSelectionDetails {
Expand All @@ -425,10 +438,15 @@ func prepareRouteSelectionDetails(routes []*selectRoute, resolvedDomains map[dom
}
domainList = append(domainList, domainResp)
}
rangeStr := r.Network.String()
for _, extra := range r.extraNetworks {
rangeStr += ", " + extra.String()
}

domainDetails := DomainDetails{items: domainList}
routeSelection = append(routeSelection, RoutesSelectionInfo{
ID: r.NetID,
Network: r.Network.String(),
Network: rangeStr,
Domains: &domainDetails,
Selected: r.Selected,
})
Expand Down Expand Up @@ -456,7 +474,9 @@ func (c *Client) SelectRoute(id string) error {
} else {
log.Debugf("select route with id: %s", id)
routes := toNetIDs([]string{id})
if err := routeSelector.SelectRoutes(routes, true, maps.Keys(routeManager.GetClientRoutesWithNetID())); err != nil {
routesMap := routeManager.GetClientRoutesWithNetID()
routes = route.ExpandV6ExitPairs(routes, routesMap)
if err := routeSelector.SelectRoutes(routes, true, maps.Keys(routesMap)); err != nil {
log.Debugf("error when selecting routes: %s", err)
return fmt.Errorf("select routes: %w", err)
}
Expand All @@ -483,7 +503,9 @@ func (c *Client) DeselectRoute(id string) error {
} else {
log.Debugf("deselect route with id: %s", id)
routes := toNetIDs([]string{id})
if err := routeSelector.DeselectRoutes(routes, maps.Keys(routeManager.GetClientRoutesWithNetID())); err != nil {
routesMap := routeManager.GetClientRoutesWithNetID()
routes = route.ExpandV6ExitPairs(routes, routesMap)
if err := routeSelector.DeselectRoutes(routes, maps.Keys(routesMap)); err != nil {
log.Debugf("error when deselecting routes: %s", err)
return fmt.Errorf("deselect routes: %w", err)
}
Expand Down
42 changes: 33 additions & 9 deletions client/server/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import (
)

type selectRoute struct {
NetID route.NetID
Network netip.Prefix
Domains domain.List
Selected bool
NetID route.NetID
Network netip.Prefix
Domains domain.List
Selected bool
extraNetworks []netip.Prefix
}

// ListNetworks returns a list of all available networks.
Expand All @@ -44,18 +45,33 @@ func (s *Server) ListNetworks(context.Context, *proto.ListNetworksRequest) (*pro
routesMap := routeMgr.GetClientRoutesWithNetID()
routeSelector := routeMgr.GetRouteSelector()

v6ExitMerged := route.V6ExitMergeSet(routesMap)

var routes []*selectRoute
for id, rt := range routesMap {
if len(rt) == 0 {
continue
}
route := &selectRoute{
// Skip v6 exit nodes that are merged into their v4 counterpart.
if _, ok := v6ExitMerged[id]; ok {
continue
}

r := &selectRoute{
NetID: id,
Network: rt[0].Network,
Domains: rt[0].Domains,
Selected: routeSelector.IsSelected(id),
}
routes = append(routes, route)

// Merge paired v6 exit node prefix into this entry.
v6ID := route.NetID(string(id) + route.V6ExitSuffix)
if _, ok := v6ExitMerged[v6ID]; ok {
v6Prefix := routesMap[v6ID][0].Network
r.extraNetworks = []netip.Prefix{v6Prefix}
}

routes = append(routes, r)
}

sort.Slice(routes, func(i, j int) bool {
Expand All @@ -76,9 +92,13 @@ func (s *Server) ListNetworks(context.Context, *proto.ListNetworksRequest) (*pro
resolvedDomains := s.statusRecorder.GetResolvedDomainsStates()
var pbRoutes []*proto.Network
for _, route := range routes {
rangeStr := route.Network.String()
for _, extra := range route.extraNetworks {
rangeStr += ", " + extra.String()
}
pbRoute := &proto.Network{
ID: string(route.NetID),
Range: route.Network.String(),
Range: rangeStr,
Domains: route.Domains.ToSafeStringList(),
ResolvedIPs: map[string]*proto.IPList{},
Selected: route.Selected,
Expand Down Expand Up @@ -137,7 +157,9 @@ func (s *Server) SelectNetworks(_ context.Context, req *proto.SelectNetworksRequ
routeSelector.SelectAllRoutes()
} else {
routes := toNetIDs(req.GetNetworkIDs())
netIdRoutes := maps.Keys(routeManager.GetClientRoutesWithNetID())
routesMap := routeManager.GetClientRoutesWithNetID()
routes = route.ExpandV6ExitPairs(routes, routesMap)
netIdRoutes := maps.Keys(routesMap)
if err := routeSelector.SelectRoutes(routes, req.GetAppend(), netIdRoutes); err != nil {
return nil, fmt.Errorf("select routes: %w", err)
}
Expand Down Expand Up @@ -183,7 +205,9 @@ func (s *Server) DeselectNetworks(_ context.Context, req *proto.SelectNetworksRe
routeSelector.DeselectAllRoutes()
} else {
routes := toNetIDs(req.GetNetworkIDs())
netIdRoutes := maps.Keys(routeManager.GetClientRoutesWithNetID())
routesMap := routeManager.GetClientRoutesWithNetID()
routes = route.ExpandV6ExitPairs(routes, routesMap)
netIdRoutes := maps.Keys(routesMap)
if err := routeSelector.DeselectRoutes(routes, netIdRoutes); err != nil {
return nil, fmt.Errorf("deselect routes: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion client/ui/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func getOverlappingNetworks(routes []*proto.Network) []*proto.Network {
func getExitNodeNetworks(routes []*proto.Network) []*proto.Network {
var filteredRoutes []*proto.Network
for _, route := range routes {
if route.Range == "0.0.0.0/0" || route.Range == "::/0" {
if strings.Contains(route.Range, "0.0.0.0/0") || route.Range == "::/0" {
filteredRoutes = append(filteredRoutes, route)
}
}
Expand Down
Loading
Loading