Refactor: Extract route filtering logic into helper function

Addressed code review feedback:
- Extracted duplicated exit route filtering logic into buildRouteFilterFunc
- Reused exitNode lookup in integration test to avoid duplication
- Added missing matcher package import

This improves code maintainability while preserving the same functionality.

Co-authored-by: kradalby <98431+kradalby@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-11-01 09:05:37 +00:00
parent 4fa1f4baa3
commit 1f4b645d5b
2 changed files with 34 additions and 48 deletions

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/policy/matcher"
"github.com/juanfont/headscale/hscontrol/types"
"tailscale.com/tailcfg"
"tailscale.com/types/views"
@@ -67,6 +68,33 @@ func (b *MapResponseBuilder) WithCapabilityVersion(capVer tailcfg.CapabilityVers
return b
}
// buildRouteFilterFunc creates a route filter function that includes both primary and exit routes.
// It filters routes based on ACL policy to ensure only authorized routes are visible.
func (b *MapResponseBuilder) buildRouteFilterFunc(
viewingNode types.NodeView,
matchers []matcher.Match,
) routeFilterFunc {
return func(id types.NodeID) []netip.Prefix {
// Get the peer node to check for exit routes
peer, ok := b.mapper.state.GetNodeByID(id)
if !ok {
return nil
}
// Start with primary routes (subnet routes, but not exit routes)
routes := policy.ReduceRoutes(viewingNode, b.mapper.state.GetNodePrimaryRoutes(id), matchers)
// Also filter exit routes through policy
// Only add exit routes if the viewing node has permission to use them
if exitRoutes := peer.ExitRoutes(); len(exitRoutes) > 0 {
filteredExitRoutes := policy.ReduceRoutes(viewingNode, exitRoutes, matchers)
routes = append(routes, filteredExitRoutes...)
}
return routes
}
}
// WithSelfNode adds the requesting node to the response.
func (b *MapResponseBuilder) WithSelfNode() *MapResponseBuilder {
nv, ok := b.mapper.state.GetNodeByID(b.nodeID)
@@ -78,25 +106,7 @@ func (b *MapResponseBuilder) WithSelfNode() *MapResponseBuilder {
_, matchers := b.mapper.state.Filter()
tailnode, err := tailNode(
nv, b.capVer, b.mapper.state,
func(id types.NodeID) []netip.Prefix {
// Get the peer node to check for exit routes
peer, ok := b.mapper.state.GetNodeByID(id)
if !ok {
return nil
}
// Start with primary routes (subnet routes, but not exit routes)
routes := policy.ReduceRoutes(nv, b.mapper.state.GetNodePrimaryRoutes(id), matchers)
// Also filter exit routes through policy
// Only add exit routes if the viewing node (self in this case) has permission to use them
if exitRoutes := peer.ExitRoutes(); len(exitRoutes) > 0 {
filteredExitRoutes := policy.ReduceRoutes(nv, exitRoutes, matchers)
routes = append(routes, filteredExitRoutes...)
}
return routes
},
b.buildRouteFilterFunc(nv, matchers),
b.mapper.cfg)
if err != nil {
b.addError(err)
@@ -269,25 +279,7 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) (
tailPeers, err := tailNodes(
changedViews, b.capVer, b.mapper.state,
func(id types.NodeID) []netip.Prefix {
// Get the peer node to check for exit routes
peer, ok := b.mapper.state.GetNodeByID(id)
if !ok {
return nil
}
// Start with primary routes (subnet routes, but not exit routes)
routes := policy.ReduceRoutes(node, b.mapper.state.GetNodePrimaryRoutes(id), matchers)
// Also filter exit routes through policy
// Only add exit routes if the viewing node has permission to use them
if exitRoutes := peer.ExitRoutes(); len(exitRoutes) > 0 {
filteredExitRoutes := policy.ReduceRoutes(node, exitRoutes, matchers)
routes = append(routes, filteredExitRoutes...)
}
return routes
},
b.buildRouteFilterFunc(node, matchers),
b.mapper.cfg)
if err != nil {
return nil, err