mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-05 13:49:57 +02:00
split out exit nodes from primary manager
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
8e3201b78d
commit
c047278109
@ -165,9 +165,10 @@ func Test_fullMapResponse(t *testing.T) {
|
|||||||
),
|
),
|
||||||
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
|
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
|
||||||
AllowedIPs: []netip.Prefix{
|
AllowedIPs: []netip.Prefix{
|
||||||
netip.MustParsePrefix("100.64.0.1/32"),
|
|
||||||
tsaddr.AllIPv4(),
|
tsaddr.AllIPv4(),
|
||||||
netip.MustParsePrefix("192.168.0.0/24"),
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("100.64.0.1/32"),
|
||||||
|
tsaddr.AllIPv6(),
|
||||||
},
|
},
|
||||||
PrimaryRoutes: []netip.Prefix{
|
PrimaryRoutes: []netip.Prefix{
|
||||||
netip.MustParsePrefix("192.168.0.0/24"),
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/juanfont/headscale/hscontrol/routes"
|
"github.com/juanfont/headscale/hscontrol/routes"
|
||||||
"github.com/juanfont/headscale/hscontrol/types"
|
"github.com/juanfont/headscale/hscontrol/types"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,6 +81,10 @@ func tailNode(
|
|||||||
}
|
}
|
||||||
tags = lo.Uniq(append(tags, node.ForcedTags...))
|
tags = lo.Uniq(append(tags, node.ForcedTags...))
|
||||||
|
|
||||||
|
allowed := append(node.Prefixes(), primary.PrimaryRoutes(node.ID)...)
|
||||||
|
allowed = append(allowed, node.ExitRoutes()...)
|
||||||
|
tsaddr.SortPrefixes(allowed)
|
||||||
|
|
||||||
tNode := tailcfg.Node{
|
tNode := tailcfg.Node{
|
||||||
ID: tailcfg.NodeID(node.ID), // this is the actual ID
|
ID: tailcfg.NodeID(node.ID), // this is the actual ID
|
||||||
StableID: node.ID.StableID(),
|
StableID: node.ID.StableID(),
|
||||||
|
@ -137,9 +137,10 @@ func TestTailNode(t *testing.T) {
|
|||||||
),
|
),
|
||||||
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
|
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
|
||||||
AllowedIPs: []netip.Prefix{
|
AllowedIPs: []netip.Prefix{
|
||||||
netip.MustParsePrefix("100.64.0.1/32"),
|
|
||||||
tsaddr.AllIPv4(),
|
tsaddr.AllIPv4(),
|
||||||
netip.MustParsePrefix("192.168.0.0/24"),
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
netip.MustParsePrefix("100.64.0.1/32"),
|
||||||
|
tsaddr.AllIPv6(),
|
||||||
},
|
},
|
||||||
PrimaryRoutes: []netip.Prefix{
|
PrimaryRoutes: []netip.Prefix{
|
||||||
netip.MustParsePrefix("192.168.0.0/24"),
|
netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
@ -102,12 +102,16 @@ func (pr *PrimaryRoutes) updatePrimaryLocked() bool {
|
|||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PrimaryRoutes) SetRoutes(node types.NodeID, prefix ...netip.Prefix) bool {
|
// SetRoutes sets the routes for a given Node ID and recalculates the primary routes
|
||||||
|
// of the headscale.
|
||||||
|
// It returns true if there was a change in primary routes.
|
||||||
|
// All exit routes are ignored as they are not used in primary route context.
|
||||||
|
func (pr *PrimaryRoutes) SetRoutes(node types.NodeID, prefixes ...netip.Prefix) bool {
|
||||||
pr.mu.Lock()
|
pr.mu.Lock()
|
||||||
defer pr.mu.Unlock()
|
defer pr.mu.Unlock()
|
||||||
|
|
||||||
// If no routes are being set, remove the node from the routes map.
|
// If no routes are being set, remove the node from the routes map.
|
||||||
if len(prefix) == 0 {
|
if len(prefixes) == 0 {
|
||||||
if _, ok := pr.routes[node]; ok {
|
if _, ok := pr.routes[node]; ok {
|
||||||
delete(pr.routes, node)
|
delete(pr.routes, node)
|
||||||
return pr.updatePrimaryLocked()
|
return pr.updatePrimaryLocked()
|
||||||
|
@ -366,7 +366,6 @@ func TestPrimaryRoutes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedPrimaries: map[netip.Prefix]types.NodeID{
|
expectedPrimaries: map[netip.Prefix]types.NodeID{
|
||||||
mp("192.168.1.0/24"): 1,
|
mp("192.168.1.0/24"): 1,
|
||||||
mp("0.0.0.0/0"): 1,
|
|
||||||
},
|
},
|
||||||
expectedIsPrimary: map[types.NodeID]bool{
|
expectedIsPrimary: map[types.NodeID]bool{
|
||||||
1: true,
|
1: true,
|
||||||
@ -389,6 +388,26 @@ func TestPrimaryRoutes(t *testing.T) {
|
|||||||
expectedRoutes: nil,
|
expectedRoutes: nil,
|
||||||
expectedChange: false,
|
expectedChange: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "exit-nodes",
|
||||||
|
operations: func(pr *PrimaryRoutes) bool {
|
||||||
|
pr.SetRoutes(1, mp("10.0.0.0/16"), mp("0.0.0.0/0"), mp("::/0"))
|
||||||
|
pr.SetRoutes(3, mp("0.0.0.0/0"), mp("::/0"))
|
||||||
|
return pr.SetRoutes(2, mp("0.0.0.0/0"), mp("::/0"))
|
||||||
|
},
|
||||||
|
expectedRoutes: map[types.NodeID]set.Set[netip.Prefix]{
|
||||||
|
1: {
|
||||||
|
mp("10.0.0.0/16"): {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPrimaries: map[netip.Prefix]types.NodeID{
|
||||||
|
mp("10.0.0.0/16"): 1,
|
||||||
|
},
|
||||||
|
expectedIsPrimary: map[types.NodeID]bool{
|
||||||
|
1: true,
|
||||||
|
},
|
||||||
|
expectedChange: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "concurrent-access",
|
name: "concurrent-access",
|
||||||
operations: func(pr *PrimaryRoutes) bool {
|
operations: func(pr *PrimaryRoutes) bool {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/juanfont/headscale/hscontrol/util"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
@ -222,6 +223,19 @@ func (node *Node) Prefixes() []netip.Prefix {
|
|||||||
return addrs
|
return addrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExitRoutes returns a list of both exit routes if the
|
||||||
|
// node has any exit routes enabled.
|
||||||
|
// If none are enabled, it will return nil.
|
||||||
|
func (node *Node) ExitRoutes() []netip.Prefix {
|
||||||
|
for _, route := range node.SubnetRoutes() {
|
||||||
|
if tsaddr.IsExitRoute(route) {
|
||||||
|
return tsaddr.ExitRoutes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (node *Node) IPsAsString() []string {
|
func (node *Node) IPsAsString() []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
policyv1 "github.com/juanfont/headscale/hscontrol/policy/v1"
|
policyv1 "github.com/juanfont/headscale/hscontrol/policy/v1"
|
||||||
"github.com/juanfont/headscale/hscontrol/util"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/types/ipproto"
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/views"
|
"tailscale.com/types/views"
|
||||||
|
"tailscale.com/util/slicesx"
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -125,7 +127,7 @@ func TestEnablingRoutes(t *testing.T) {
|
|||||||
for _, peerKey := range status.Peers() {
|
for _, peerKey := range status.Peers() {
|
||||||
peerStatus := status.Peer[peerKey]
|
peerStatus := status.Peer[peerKey]
|
||||||
|
|
||||||
assert.Nil(t, peerStatus.PrimaryRoutes)
|
assert.NotNil(t, peerStatus.PrimaryRoutes)
|
||||||
|
|
||||||
assert.Len(t, peerStatus.AllowedIPs.AsSlice(), 3)
|
assert.Len(t, peerStatus.AllowedIPs.AsSlice(), 3)
|
||||||
|
|
||||||
@ -189,7 +191,6 @@ func TestEnablingRoutes(t *testing.T) {
|
|||||||
for _, peerKey := range status.Peers() {
|
for _, peerKey := range status.Peers() {
|
||||||
peerStatus := status.Peer[peerKey]
|
peerStatus := status.Peer[peerKey]
|
||||||
|
|
||||||
assert.Nil(t, peerStatus.PrimaryRoutes)
|
|
||||||
if peerStatus.ID == "1" {
|
if peerStatus.ID == "1" {
|
||||||
requirePeerSubnetRoutes(t, peerStatus, nil)
|
requirePeerSubnetRoutes(t, peerStatus, nil)
|
||||||
} else if peerStatus.ID == "2" {
|
} else if peerStatus.ID == "2" {
|
||||||
@ -1378,6 +1379,32 @@ func TestSubnetRouterMultiNetwork(t *testing.T) {
|
|||||||
peerStatus := status.Peer[peerKey]
|
peerStatus := status.Peer[peerKey]
|
||||||
|
|
||||||
assert.Contains(t, peerStatus.PrimaryRoutes.AsSlice(), *pref)
|
assert.Contains(t, peerStatus.PrimaryRoutes.AsSlice(), *pref)
|
||||||
|
requirePeerSubnetRoutes(t, peerStatus, []netip.Prefix{*pref})
|
||||||
|
}
|
||||||
|
|
||||||
|
usernet1, err := scenario.Network("usernet1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
services, err := scenario.Services("usernet1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, services, 1)
|
||||||
|
|
||||||
|
web := services[0]
|
||||||
|
webip := netip.MustParseAddr(web.GetIPInNetwork(usernet1))
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", webip)
|
||||||
|
t.Logf("url from %s to %s", user2c.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := user2c.Curl(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, result, 13)
|
||||||
|
|
||||||
|
tr, err := user2c.Traceroute(webip)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertTracerouteViaIP(t, tr, user1c.MustIPv4())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSubnetRouterMultiNetworkExitNode
|
||||||
func TestSubnetRouterMultiNetworkExitNode(t *testing.T) {
|
func TestSubnetRouterMultiNetworkExitNode(t *testing.T) {
|
||||||
IntegrationSkip(t)
|
IntegrationSkip(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@ -1510,32 +1537,6 @@ func TestSubnetRouterMultiNetworkExitNode(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Nil(t, peerStatus.PrimaryRoutes)
|
|
||||||
requirePeerSubnetRoutes(t, peerStatus, []netip.Prefix{*pref})
|
|
||||||
}
|
|
||||||
|
|
||||||
usernet1, err := scenario.Network("usernet1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
services, err := scenario.Services("usernet1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, services, 1)
|
|
||||||
|
|
||||||
web := services[0]
|
|
||||||
webip := netip.MustParseAddr(web.GetIPInNetwork(usernet1))
|
|
||||||
|
|
||||||
url := fmt.Sprintf("http://%s/etc/hostname", webip)
|
|
||||||
t.Logf("url from %s to %s", user2c.Hostname(), url)
|
|
||||||
|
|
||||||
result, err := user2c.Curl(url)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, result, 13)
|
|
||||||
|
|
||||||
tr, err := user2c.Traceroute(webip)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assertTracerouteViaIP(t, tr, user1c.MustIPv4())
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertTracerouteViaIP(t *testing.T, tr util.Traceroute, ip netip.Addr) {
|
func assertTracerouteViaIP(t *testing.T, tr util.Traceroute, ip netip.Addr) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user