mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-01 13:46:49 +02:00
wire through primare router
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
d3bce2d7ff
commit
a48843bfd0
@ -572,7 +572,7 @@ func (h *Headscale) Serve() error {
|
||||
|
||||
// Fetch an initial DERP Map before we start serving
|
||||
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
|
||||
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier, h.polMan)
|
||||
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier, h.polMan, h.primaryRoutes)
|
||||
|
||||
if h.cfg.DERP.ServerEnabled {
|
||||
// When embedded DERP is enabled we always need a STUN server
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/juanfont/headscale/hscontrol/db"
|
||||
"github.com/juanfont/headscale/hscontrol/notifier"
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/routes"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
@ -56,6 +57,7 @@ type Mapper struct {
|
||||
derpMap *tailcfg.DERPMap
|
||||
notif *notifier.Notifier
|
||||
polMan policy.PolicyManager
|
||||
primary *routes.PrimaryRoutes
|
||||
|
||||
uid string
|
||||
created time.Time
|
||||
@ -73,6 +75,7 @@ func NewMapper(
|
||||
derpMap *tailcfg.DERPMap,
|
||||
notif *notifier.Notifier,
|
||||
polMan policy.PolicyManager,
|
||||
primary *routes.PrimaryRoutes,
|
||||
) *Mapper {
|
||||
uid, _ := util.GenerateRandomStringDNSSafe(mapperIDLength)
|
||||
|
||||
@ -82,6 +85,7 @@ func NewMapper(
|
||||
derpMap: derpMap,
|
||||
notif: notif,
|
||||
polMan: polMan,
|
||||
primary: primary,
|
||||
|
||||
uid: uid,
|
||||
created: time.Now(),
|
||||
@ -166,6 +170,7 @@ func (m *Mapper) fullMapResponse(
|
||||
resp,
|
||||
true, // full change
|
||||
m.polMan,
|
||||
m.primary,
|
||||
node,
|
||||
capVer,
|
||||
peers,
|
||||
@ -271,6 +276,7 @@ func (m *Mapper) PeerChangedResponse(
|
||||
&resp,
|
||||
false, // partial change
|
||||
m.polMan,
|
||||
m.primary,
|
||||
node,
|
||||
mapRequest.Version,
|
||||
changedNodes,
|
||||
@ -299,7 +305,7 @@ func (m *Mapper) PeerChangedResponse(
|
||||
|
||||
// Add the node itself, it might have changed, and particularly
|
||||
// if there are no patches or changes, this is a self update.
|
||||
tailnode, err := tailNode(node, mapRequest.Version, m.polMan, m.cfg)
|
||||
tailnode, err := tailNode(node, mapRequest.Version, m.polMan, m.primary, m.cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -446,7 +452,7 @@ func (m *Mapper) baseWithConfigMapResponse(
|
||||
) (*tailcfg.MapResponse, error) {
|
||||
resp := m.baseMapResponse()
|
||||
|
||||
tailnode, err := tailNode(node, capVer, m.polMan, m.cfg)
|
||||
tailnode, err := tailNode(node, capVer, m.polMan, m.primary, m.cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -500,6 +506,7 @@ func appendPeerChanges(
|
||||
|
||||
fullChange bool,
|
||||
polMan policy.PolicyManager,
|
||||
primary *routes.PrimaryRoutes,
|
||||
node *types.Node,
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
changed types.Nodes,
|
||||
@ -522,7 +529,7 @@ func appendPeerChanges(
|
||||
|
||||
dnsConfig := generateDNSConfig(cfg, node)
|
||||
|
||||
tailPeers, err := tailNodes(changed, capVer, polMan, cfg)
|
||||
tailPeers, err := tailNodes(changed, capVer, polMan, primary, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/routes"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"gopkg.in/check.v1"
|
||||
"gorm.io/gorm"
|
||||
@ -159,11 +159,11 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
lastSeen := time.Date(2009, time.November, 10, 23, 9, 0, 0, time.UTC)
|
||||
expire := time.Date(2500, time.November, 11, 23, 0, 0, 0, time.UTC)
|
||||
|
||||
user1 := types.User{Model: gorm.Model{ID: 0}, Name: "mini"}
|
||||
user2 := types.User{Model: gorm.Model{ID: 1}, Name: "peer2"}
|
||||
user1 := types.User{Model: gorm.Model{ID: 1}, Name: "user1"}
|
||||
user2 := types.User{Model: gorm.Model{ID: 2}, Name: "user2"}
|
||||
|
||||
mini := &types.Node{
|
||||
ID: 0,
|
||||
ID: 1,
|
||||
MachineKey: mustMK(
|
||||
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
),
|
||||
@ -194,10 +194,10 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
tailMini := &tailcfg.Node{
|
||||
ID: 0,
|
||||
StableID: "0",
|
||||
ID: 1,
|
||||
StableID: "1",
|
||||
Name: "mini",
|
||||
User: 0,
|
||||
User: tailcfg.UserID(user1.ID),
|
||||
Key: mustNK(
|
||||
"nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
),
|
||||
@ -214,12 +214,17 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
tsaddr.AllIPv4(),
|
||||
netip.MustParsePrefix("192.168.0.0/24"),
|
||||
},
|
||||
HomeDERP: 0,
|
||||
LegacyDERPString: "127.3.3.40:0",
|
||||
Hostinfo: hiview(tailcfg.Hostinfo{}),
|
||||
HomeDERP: 0,
|
||||
LegacyDERPString: "127.3.3.40:0",
|
||||
Hostinfo: hiview(tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{
|
||||
tsaddr.AllIPv4(),
|
||||
netip.MustParsePrefix("192.168.0.0/24"),
|
||||
netip.MustParsePrefix("172.0.0.0/10"),
|
||||
},
|
||||
}),
|
||||
Created: created,
|
||||
Tags: []string{},
|
||||
PrimaryRoutes: []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")},
|
||||
LastSeen: &lastSeen,
|
||||
MachineAuthorized: true,
|
||||
|
||||
@ -231,7 +236,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
peer1 := &types.Node{
|
||||
ID: 1,
|
||||
ID: 2,
|
||||
MachineKey: mustMK(
|
||||
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
),
|
||||
@ -244,8 +249,8 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
IPv4: iap("100.64.0.2"),
|
||||
Hostname: "peer1",
|
||||
GivenName: "peer1",
|
||||
UserID: user1.ID,
|
||||
User: user1,
|
||||
UserID: user2.ID,
|
||||
User: user2,
|
||||
ForcedTags: []string{},
|
||||
LastSeen: &lastSeen,
|
||||
Expiry: &expire,
|
||||
@ -254,9 +259,10 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
tailPeer1 := &tailcfg.Node{
|
||||
ID: 1,
|
||||
StableID: "1",
|
||||
ID: 2,
|
||||
StableID: "2",
|
||||
Name: "peer1",
|
||||
User: tailcfg.UserID(user2.ID),
|
||||
Key: mustNK(
|
||||
"nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
),
|
||||
@ -274,7 +280,6 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
Hostinfo: hiview(tailcfg.Hostinfo{}),
|
||||
Created: created,
|
||||
Tags: []string{},
|
||||
PrimaryRoutes: []netip.Prefix{},
|
||||
LastSeen: &lastSeen,
|
||||
MachineAuthorized: true,
|
||||
|
||||
@ -285,29 +290,6 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
peer2 := &types.Node{
|
||||
ID: 2,
|
||||
MachineKey: mustMK(
|
||||
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
),
|
||||
NodeKey: mustNK(
|
||||
"nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
),
|
||||
DiscoKey: mustDK(
|
||||
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
|
||||
),
|
||||
IPv4: iap("100.64.0.3"),
|
||||
Hostname: "peer2",
|
||||
GivenName: "peer2",
|
||||
UserID: user2.ID,
|
||||
User: user2,
|
||||
ForcedTags: []string{},
|
||||
LastSeen: &lastSeen,
|
||||
Expiry: &expire,
|
||||
Hostinfo: &tailcfg.Hostinfo{},
|
||||
CreatedAt: created,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pol *policy.ACLPolicy
|
||||
@ -349,7 +331,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
Domain: "",
|
||||
CollectServices: "false",
|
||||
PacketFilter: []tailcfg.FilterRule{},
|
||||
UserProfiles: []tailcfg.UserProfile{{LoginName: "mini", DisplayName: "mini"}},
|
||||
UserProfiles: []tailcfg.UserProfile{{ID: tailcfg.UserID(user1.ID), LoginName: "user1", DisplayName: "user1"}},
|
||||
SSHPolicy: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}},
|
||||
ControlTime: &time.Time{},
|
||||
Debug: &tailcfg.Debug{
|
||||
@ -383,9 +365,12 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
Domain: "",
|
||||
CollectServices: "false",
|
||||
PacketFilter: []tailcfg.FilterRule{},
|
||||
UserProfiles: []tailcfg.UserProfile{{LoginName: "mini", DisplayName: "mini"}},
|
||||
SSHPolicy: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}},
|
||||
ControlTime: &time.Time{},
|
||||
UserProfiles: []tailcfg.UserProfile{
|
||||
{ID: tailcfg.UserID(user1.ID), LoginName: "user1", DisplayName: "user1"},
|
||||
{ID: tailcfg.UserID(user2.ID), LoginName: "user2", DisplayName: "user2"},
|
||||
},
|
||||
SSHPolicy: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}},
|
||||
ControlTime: &time.Time{},
|
||||
Debug: &tailcfg.Debug{
|
||||
DisableLogTail: true,
|
||||
},
|
||||
@ -395,6 +380,9 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
{
|
||||
name: "with-pol-map-response",
|
||||
pol: &policy.ACLPolicy{
|
||||
Hosts: policy.Hosts{
|
||||
"mini": netip.MustParsePrefix("100.64.0.1/32"),
|
||||
},
|
||||
ACLs: []policy.ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
@ -406,7 +394,6 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
node: mini,
|
||||
peers: types.Nodes{
|
||||
peer1,
|
||||
peer2,
|
||||
},
|
||||
derpMap: &tailcfg.DERPMap{},
|
||||
cfg: &types.Config{
|
||||
@ -434,7 +421,8 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
},
|
||||
},
|
||||
UserProfiles: []tailcfg.UserProfile{
|
||||
{LoginName: "mini", DisplayName: "mini"},
|
||||
{ID: tailcfg.UserID(user1.ID), LoginName: "user1", DisplayName: "user1"},
|
||||
{ID: tailcfg.UserID(user2.ID), LoginName: "user2", DisplayName: "user2"},
|
||||
},
|
||||
SSHPolicy: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}},
|
||||
ControlTime: &time.Time{},
|
||||
@ -449,6 +437,12 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
polMan, _ := policy.NewPolicyManagerForTest(tt.pol, []types.User{user1, user2}, append(tt.peers, tt.node))
|
||||
primary := routes.New()
|
||||
|
||||
primary.RegisterRoutes(tt.node.ID, tt.node.SubnetRoutes()...)
|
||||
for _, peer := range tt.peers {
|
||||
primary.RegisterRoutes(peer.ID, peer.SubnetRoutes()...)
|
||||
}
|
||||
|
||||
mappy := NewMapper(
|
||||
nil,
|
||||
@ -456,6 +450,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
tt.derpMap,
|
||||
nil,
|
||||
polMan,
|
||||
primary,
|
||||
)
|
||||
|
||||
got, err := mappy.fullMapResponse(
|
||||
@ -470,8 +465,6 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
spew.Dump(got)
|
||||
|
||||
if diff := cmp.Diff(
|
||||
tt.want,
|
||||
got,
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/routes"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/samber/lo"
|
||||
"tailscale.com/tailcfg"
|
||||
@ -15,6 +16,7 @@ func tailNodes(
|
||||
nodes types.Nodes,
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
polMan policy.PolicyManager,
|
||||
primary *routes.PrimaryRoutes,
|
||||
cfg *types.Config,
|
||||
) ([]*tailcfg.Node, error) {
|
||||
tNodes := make([]*tailcfg.Node, len(nodes))
|
||||
@ -24,6 +26,7 @@ func tailNodes(
|
||||
node,
|
||||
capVer,
|
||||
polMan,
|
||||
primary,
|
||||
cfg,
|
||||
)
|
||||
if err != nil {
|
||||
@ -41,6 +44,7 @@ func tailNode(
|
||||
node *types.Node,
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
polMan policy.PolicyManager,
|
||||
primary *routes.PrimaryRoutes,
|
||||
cfg *types.Config,
|
||||
) (*tailcfg.Node, error) {
|
||||
addrs := node.Prefixes()
|
||||
@ -94,6 +98,7 @@ func tailNode(
|
||||
Machine: node.MachineKey,
|
||||
DiscoKey: node.DiscoKey,
|
||||
Addresses: addrs,
|
||||
PrimaryRoutes: primary.PrimaryRoutes(node.ID),
|
||||
AllowedIPs: allowedIPs,
|
||||
Endpoints: node.Endpoints,
|
||||
HomeDERP: derp,
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/routes"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
@ -72,7 +73,6 @@ func TestTailNode(t *testing.T) {
|
||||
LegacyDERPString: "127.3.3.40:0",
|
||||
Hostinfo: hiview(tailcfg.Hostinfo{}),
|
||||
Tags: []string{},
|
||||
PrimaryRoutes: []netip.Prefix{},
|
||||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
@ -146,8 +146,14 @@ func TestTailNode(t *testing.T) {
|
||||
},
|
||||
HomeDERP: 0,
|
||||
LegacyDERPString: "127.3.3.40:0",
|
||||
Hostinfo: hiview(tailcfg.Hostinfo{}),
|
||||
Created: created,
|
||||
Hostinfo: hiview(tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{
|
||||
tsaddr.AllIPv4(),
|
||||
netip.MustParsePrefix("192.168.0.0/24"),
|
||||
netip.MustParsePrefix("172.0.0.0/10"),
|
||||
},
|
||||
}),
|
||||
Created: created,
|
||||
|
||||
Tags: []string{},
|
||||
|
||||
@ -174,15 +180,22 @@ func TestTailNode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
polMan, _ := policy.NewPolicyManagerForTest(tt.pol, []types.User{}, types.Nodes{tt.node})
|
||||
primary := routes.New()
|
||||
cfg := &types.Config{
|
||||
BaseDomain: tt.baseDomain,
|
||||
TailcfgDNSConfig: tt.dnsConfig,
|
||||
RandomizeClientPort: false,
|
||||
}
|
||||
_ = primary.RegisterRoutes(tt.node.ID, tt.node.SubnetRoutes()...)
|
||||
|
||||
// This is a hack to avoid having a second node to test the primary route.
|
||||
// This should be baked into the test case proper if it is extended in the future.
|
||||
_ = primary.RegisterRoutes(2, netip.MustParsePrefix("192.168.0.0/24"))
|
||||
got, err := tailNode(
|
||||
tt.node,
|
||||
0,
|
||||
polMan,
|
||||
primary,
|
||||
cfg,
|
||||
)
|
||||
|
||||
@ -236,6 +249,7 @@ func TestNodeExpiry(t *testing.T) {
|
||||
node,
|
||||
0,
|
||||
&policy.PolicyManagerV1{},
|
||||
nil,
|
||||
&types.Config{},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -208,6 +208,7 @@ func (pol *ACLPolicy) CompileFilterRules(
|
||||
users,
|
||||
alias,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -243,6 +244,7 @@ func (pol *ACLPolicy) CompileFilterRules(
|
||||
// ReduceFilterRules takes a node and a set of rules and removes all rules and destinations
|
||||
// that are not relevant to that particular node.
|
||||
func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.FilterRule {
|
||||
// TODO(kradalby): Make this nil and not alloc unless needed
|
||||
ret := []tailcfg.FilterRule{}
|
||||
|
||||
for _, rule := range rules {
|
||||
@ -264,13 +266,11 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
|
||||
|
||||
// If the node exposes routes, ensure they are note removed
|
||||
// when the filters are reduced.
|
||||
if node.Hostinfo != nil {
|
||||
if len(node.Hostinfo.RoutableIPs) > 0 {
|
||||
for _, routableIP := range node.Hostinfo.RoutableIPs {
|
||||
if expanded.OverlapsPrefix(routableIP) {
|
||||
dests = append(dests, dest)
|
||||
continue DEST_LOOP
|
||||
}
|
||||
if len(node.SubnetRoutes()) > 0 {
|
||||
for _, routableIP := range node.SubnetRoutes() {
|
||||
if expanded.OverlapsPrefix(routableIP) {
|
||||
dests = append(dests, dest)
|
||||
continue DEST_LOOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
@ -108,9 +107,7 @@ type Node struct {
|
||||
IsOnline *bool `gorm:"-"`
|
||||
}
|
||||
|
||||
type (
|
||||
Nodes []*Node
|
||||
)
|
||||
type Nodes []*Node
|
||||
|
||||
// GivenNameHasBeenChanged returns whether the `givenName` can be automatically changed based on the `Hostname` of the node.
|
||||
func (node *Node) GivenNameHasBeenChanged() bool {
|
||||
|
@ -74,3 +74,21 @@ func TailMapResponseToString(resp tailcfg.MapResponse) string {
|
||||
TailNodesToString(resp.Peers),
|
||||
)
|
||||
}
|
||||
|
||||
func TailcfgFilterRulesToString(rules []tailcfg.FilterRule) string {
|
||||
var sb strings.Builder
|
||||
|
||||
for index, rule := range rules {
|
||||
sb.WriteString(fmt.Sprintf(`
|
||||
{
|
||||
SrcIPs: %v
|
||||
DstIPs: %v
|
||||
}
|
||||
`, rule.SrcIPs, rule.DstPorts))
|
||||
if index < len(rules)-1 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[ %s ](%d)", sb.String(), len(rules))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user