@@ -269,11 +269,19 @@ func (node *Node) Prefixes() []netip.Prefix {
269269// node has any exit routes enabled.
270270// If none are enabled, it will return nil.
271271func (node * Node ) ExitRoutes () []netip.Prefix {
272- if slices .ContainsFunc (node .SubnetRoutes (), tsaddr .IsExitRoute ) {
273- return tsaddr .ExitRoutes ()
272+ var routes []netip.Prefix
273+
274+ for _ , route := range node .AnnouncedRoutes () {
275+ if tsaddr .IsExitRoute (route ) && slices .Contains (node .ApprovedRoutes , route ) {
276+ routes = append (routes , route )
277+ }
274278 }
275279
276- return nil
280+ return routes
281+ }
282+
283+ func (node * Node ) IsExitNode () bool {
284+ return len (node .ExitRoutes ()) > 0
277285}
278286
279287func (node * Node ) IPsAsString () []string {
@@ -440,16 +448,22 @@ func (node *Node) AnnouncedRoutes() []netip.Prefix {
440448 return node .Hostinfo .RoutableIPs
441449}
442450
443- // SubnetRoutes returns the list of routes that the node announces and are approved.
451+ // SubnetRoutes returns the list of routes (excluding exit routes) that the node
452+ // announces and are approved.
444453//
445- // IMPORTANT: This method is used for internal data structures and should NOT be used
446- // for the gRPC Proto conversion. For Proto, SubnetRoutes must be populated manually
447- // with PrimaryRoutes to ensure it includes only routes actively served by the node.
448- // See the comment in Proto() method and the implementation in grpcv1.go/nodesToProto.
454+ // IMPORTANT: This method is used for internal data structures and should NOT be
455+ // used for the gRPC Proto conversion. For Proto, SubnetRoutes must be populated
456+ // manually with PrimaryRoutes to ensure it includes only routes actively served
457+ // by the node. See the comment in Proto() method and the implementation in
458+ // grpcv1.go/nodesToProto.
449459func (node * Node ) SubnetRoutes () []netip.Prefix {
450460 var routes []netip.Prefix
451461
452462 for _ , route := range node .AnnouncedRoutes () {
463+ if tsaddr .IsExitRoute (route ) {
464+ continue
465+ }
466+
453467 if slices .Contains (node .ApprovedRoutes , route ) {
454468 routes = append (routes , route )
455469 }
@@ -463,6 +477,11 @@ func (node *Node) IsSubnetRouter() bool {
463477 return len (node .SubnetRoutes ()) > 0
464478}
465479
480+ // AllApprovedRoutes returns the combination of SubnetRoutes and ExitRoutes
481+ func (node * Node ) AllApprovedRoutes () []netip.Prefix {
482+ return append (node .SubnetRoutes (), node .ExitRoutes ()... )
483+ }
484+
466485func (node * Node ) String () string {
467486 return node .Hostname
468487}
@@ -653,6 +672,7 @@ func (node Node) DebugString() string {
653672 fmt .Fprintf (& sb , "\t ApprovedRoutes: %v\n " , node .ApprovedRoutes )
654673 fmt .Fprintf (& sb , "\t AnnouncedRoutes: %v\n " , node .AnnouncedRoutes ())
655674 fmt .Fprintf (& sb , "\t SubnetRoutes: %v\n " , node .SubnetRoutes ())
675+ fmt .Fprintf (& sb , "\t ExitRoutes: %v\n " , node .ExitRoutes ())
656676 sb .WriteString ("\n " )
657677
658678 return sb .String ()
@@ -730,6 +750,13 @@ func (v NodeView) IsSubnetRouter() bool {
730750 return v .ж .IsSubnetRouter ()
731751}
732752
753+ func (v NodeView ) AllApprovedRoutes () []netip.Prefix {
754+ if ! v .Valid () {
755+ return nil
756+ }
757+ return v .ж .AllApprovedRoutes ()
758+ }
759+
733760func (v NodeView ) AppendToIPSet (build * netipx.IPSetBuilder ) {
734761 if ! v .Valid () {
735762 return
@@ -808,6 +835,13 @@ func (v NodeView) ExitRoutes() []netip.Prefix {
808835 return v .ж .ExitRoutes ()
809836}
810837
838+ func (v NodeView ) IsExitNode () bool {
839+ if ! v .Valid () {
840+ return false
841+ }
842+ return v .ж .IsExitNode ()
843+ }
844+
811845// RequestTags returns the ACL tags that the node is requesting.
812846func (v NodeView ) RequestTags () []string {
813847 if ! v .Valid () || ! v .Hostinfo ().Valid () {
0 commit comments