From c1a05f451bfe2302c428a9d876a8832697bdd58e Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 12 Apr 2024 19:39:42 +0200 Subject: [PATCH] use ipv4/6 everywhere instead of address list Updates #1828 Signed-off-by: Kristoffer Dalby --- hscontrol/auth.go | 5 +- hscontrol/db/ip.go | 58 ++- hscontrol/db/ip_test.go | 93 ++-- hscontrol/db/node_test.go | 35 +- hscontrol/db/routes.go | 4 +- hscontrol/grpcv1.go | 4 +- hscontrol/mapper/mapper.go | 4 +- hscontrol/mapper/mapper_test.go | 71 +-- hscontrol/mapper/tail.go | 2 +- hscontrol/mapper/tail_test.go | 4 +- hscontrol/oidc.go | 4 +- hscontrol/policy/acls.go | 18 +- hscontrol/policy/acls_test.go | 785 +++++++++++++------------------- hscontrol/types/node.go | 203 +++++---- hscontrol/types/node_test.go | 50 +- hscontrol/util/dns_test.go | 20 +- 16 files changed, 586 insertions(+), 774 deletions(-) diff --git a/hscontrol/auth.go b/hscontrol/auth.go index 8271038cc1..8307d31434 100644 --- a/hscontrol/auth.go +++ b/hscontrol/auth.go @@ -383,7 +383,7 @@ func (h *Headscale) handleAuthKey( ForcedTags: pak.Proto().GetAclTags(), } - addrs, err := h.ipAlloc.Next() + ipv4, ipv6, err := h.ipAlloc.Next() if err != nil { log.Error(). Caller(). @@ -397,7 +397,7 @@ func (h *Headscale) handleAuthKey( node, err = h.db.RegisterNode( nodeToRegister, - addrs, + ipv4, ipv6, ) if err != nil { log.Error(). @@ -461,7 +461,6 @@ func (h *Headscale) handleAuthKey( log.Info(). Str("node", registerRequest.Hostinfo.Hostname). - Str("ips", strings.Join(node.IPAddresses.StringSlice(), ", ")). Msg("Successfully authenticated via AuthKey") } diff --git a/hscontrol/db/ip.go b/hscontrol/db/ip.go index 2a594f9ed7..800ad41f6b 100644 --- a/hscontrol/db/ip.go +++ b/hscontrol/db/ip.go @@ -1,6 +1,7 @@ package db import ( + "database/sql" "errors" "fmt" "net/netip" @@ -46,12 +47,24 @@ func NewIPAllocator(db *HSDatabase, prefix4, prefix6 *netip.Prefix) (*IPAllocato prefix6: prefix6, } - var addressesSlices []string + var v4s []sql.NullString + var v6s []sql.NullString if db != nil { - db.Read(func(rx *gorm.DB) error { - return rx.Model(&types.Node{}).Pluck("ip_addresses", &addressesSlices).Error + err := db.Read(func(rx *gorm.DB) error { + return rx.Model(&types.Node{}).Pluck("ipv4", &v4s).Error }) + if err != nil { + return nil, fmt.Errorf("reading IPv4 addresses from database: %w", err) + } + + err = db.Read(func(rx *gorm.DB) error { + return rx.Model(&types.Node{}).Pluck("ipv6", &v6s).Error + }) + if err != nil { + return nil, fmt.Errorf("reading IPv6 addresses from database: %w", err) + } + } var ips netipx.IPSetBuilder @@ -79,18 +92,14 @@ func NewIPAllocator(db *HSDatabase, prefix4, prefix6 *netip.Prefix) (*IPAllocato // Fetch all the IP Addresses currently handed out from the Database // and add them to the used IP set. - for _, slice := range addressesSlices { - var machineAddresses types.NodeAddresses - err := machineAddresses.Scan(slice) - if err != nil { - return nil, fmt.Errorf( - "parsing IPs from database %v: %w", machineAddresses, - err, - ) - } - - for _, ip := range machineAddresses { - ips.Add(ip) + for _, addrStr := range append(v4s, v6s...) { + if addrStr.Valid { + addr, err := netip.ParseAddr(addrStr.String) + if err != nil { + return nil, fmt.Errorf("parsing IP address from database: %w", err) + } + + ips.Add(addr) } } @@ -108,31 +117,30 @@ func NewIPAllocator(db *HSDatabase, prefix4, prefix6 *netip.Prefix) (*IPAllocato return &ret, nil } -func (i *IPAllocator) Next() (types.NodeAddresses, error) { +func (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) { i.mu.Lock() defer i.mu.Unlock() - var ret types.NodeAddresses + var err error + var ret4 *netip.Addr + var ret6 *netip.Addr if i.prefix4 != nil { - v4, err := i.next(i.prev4, i.prefix4) + ret4, err = i.next(i.prev4, i.prefix4) if err != nil { - return nil, fmt.Errorf("allocating IPv4 address: %w", err) + return nil, nil, fmt.Errorf("allocating IPv4 address: %w", err) } - ret = append(ret, *v4) } if i.prefix6 != nil { - v6, err := i.next(i.prev6, i.prefix6) + ret6, err = i.next(i.prev6, i.prefix6) if err != nil { - return nil, fmt.Errorf("allocating IPv6 address: %w", err) + return nil, nil, fmt.Errorf("allocating IPv6 address: %w", err) } - - ret = append(ret, *v6) } - return ret, nil + return ret4, ret6, nil } var ErrCouldNotAllocateIP = errors.New("failed to allocate IP") diff --git a/hscontrol/db/ip_test.go b/hscontrol/db/ip_test.go index 7dd3e70e0b..02bb132f02 100644 --- a/hscontrol/db/ip_test.go +++ b/hscontrol/db/ip_test.go @@ -1,6 +1,7 @@ package db import ( + "database/sql" "net/netip" "os" "testing" @@ -44,7 +45,8 @@ func TestIPAllocator(t *testing.T) { prefix4 *netip.Prefix prefix6 *netip.Prefix getCount int - want []types.NodeAddresses + want4 []netip.Addr + want6 []netip.Addr }{ { name: "simple", @@ -57,11 +59,11 @@ func TestIPAllocator(t *testing.T) { getCount: 1, - want: []types.NodeAddresses{ - { - na("100.64.0.1"), - na("fd7a:115c:a1e0::1"), - }, + want4: []netip.Addr{ + na("100.64.0.1"), + }, + want6: []netip.Addr{ + na("fd7a:115c:a1e0::1"), }, }, { @@ -74,10 +76,8 @@ func TestIPAllocator(t *testing.T) { getCount: 1, - want: []types.NodeAddresses{ - { - na("100.64.0.1"), - }, + want4: []netip.Addr{ + na("100.64.0.1"), }, }, { @@ -90,10 +90,8 @@ func TestIPAllocator(t *testing.T) { getCount: 1, - want: []types.NodeAddresses{ - { - na("fd7a:115c:a1e0::1"), - }, + want6: []netip.Addr{ + na("fd7a:115c:a1e0::1"), }, }, { @@ -102,9 +100,13 @@ func TestIPAllocator(t *testing.T) { db := newDb() db.DB.Save(&types.Node{ - IPAddresses: types.NodeAddresses{ - na("100.64.0.1"), - na("fd7a:115c:a1e0::1"), + IPv4DatabaseField: sql.NullString{ + Valid: true, + String: "100.64.0.1", + }, + IPv6DatabaseField: sql.NullString{ + Valid: true, + String: "fd7a:115c:a1e0::1", }, }) @@ -116,11 +118,11 @@ func TestIPAllocator(t *testing.T) { getCount: 1, - want: []types.NodeAddresses{ - { - na("100.64.0.2"), - na("fd7a:115c:a1e0::2"), - }, + want4: []netip.Addr{ + na("100.64.0.2"), + }, + want6: []netip.Addr{ + na("fd7a:115c:a1e0::2"), }, }, { @@ -129,9 +131,13 @@ func TestIPAllocator(t *testing.T) { db := newDb() db.DB.Save(&types.Node{ - IPAddresses: types.NodeAddresses{ - na("100.64.0.2"), - na("fd7a:115c:a1e0::2"), + IPv4DatabaseField: sql.NullString{ + Valid: true, + String: "100.64.0.2", + }, + IPv6DatabaseField: sql.NullString{ + Valid: true, + String: "fd7a:115c:a1e0::2", }, }) @@ -143,15 +149,13 @@ func TestIPAllocator(t *testing.T) { getCount: 2, - want: []types.NodeAddresses{ - { - na("100.64.0.1"), - na("fd7a:115c:a1e0::1"), - }, - { - na("100.64.0.3"), - na("fd7a:115c:a1e0::3"), - }, + want4: []netip.Addr{ + na("100.64.0.1"), + na("100.64.0.3"), + }, + want6: []netip.Addr{ + na("fd7a:115c:a1e0::1"), + na("fd7a:115c:a1e0::3"), }, }, } @@ -164,18 +168,29 @@ func TestIPAllocator(t *testing.T) { spew.Dump(alloc) - var got []types.NodeAddresses + var got4s []netip.Addr + var got6s []netip.Addr for range tt.getCount { - gotSet, err := alloc.Next() + got4, got6, err := alloc.Next() if err != nil { t.Fatalf("allocating next IP: %s", err) } - got = append(got, gotSet) + if got4 != nil { + got4s = append(got4s, *got4) + } + + if got6 != nil { + got6s = append(got6s, *got6) + } } - if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" { - t.Errorf("IPAllocator unexpected result (-want +got):\n%s", diff) + if diff := cmp.Diff(tt.want4, got4s, util.Comparers...); diff != "" { + t.Errorf("IPAllocator 4s unexpected result (-want +got):\n%s", diff) + } + + if diff := cmp.Diff(tt.want6, got6s, util.Comparers...); diff != "" { + t.Errorf("IPAllocator 6s unexpected result (-want +got):\n%s", diff) } }) } diff --git a/hscontrol/db/node_test.go b/hscontrol/db/node_test.go index 0dbe7688ef..84116f25b6 100644 --- a/hscontrol/db/node_test.go +++ b/hscontrol/db/node_test.go @@ -188,13 +188,12 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) { nodeKey := key.NewNode() machineKey := key.NewMachine() + v4 := netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))) node := types.Node{ - ID: types.NodeID(index), - MachineKey: machineKey.Public(), - NodeKey: nodeKey.Public(), - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))), - }, + ID: types.NodeID(index), + MachineKey: machineKey.Public(), + NodeKey: nodeKey.Public(), + IPv4: &v4, Hostname: "testnode" + strconv.Itoa(index), UserID: stor[index%2].user.ID, RegisterMethod: util.RegisterMethodAuthKey, @@ -301,27 +300,6 @@ func (s *Suite) TestExpireNode(c *check.C) { c.Assert(nodeFromDB.IsExpired(), check.Equals, true) } -func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) { - input := types.NodeAddresses([]netip.Addr{ - netip.MustParseAddr("192.0.2.1"), - netip.MustParseAddr("2001:db8::1"), - }) - serialized, err := input.Value() - c.Assert(err, check.IsNil) - if serial, ok := serialized.(string); ok { - c.Assert(serial, check.Equals, "192.0.2.1,2001:db8::1") - } - - var deserialized types.NodeAddresses - err = deserialized.Scan(serialized) - c.Assert(err, check.IsNil) - - c.Assert(len(deserialized), check.Equals, len(input)) - for i := range deserialized { - c.Assert(deserialized[i], check.Equals, input[i]) - } -} - func (s *Suite) TestGenerateGivenName(c *check.C) { user1, err := db.CreateUser("user-1") c.Assert(err, check.IsNil) @@ -561,6 +539,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) { // Check if a subprefix of an autoapproved route is approved route2 := netip.MustParsePrefix("10.11.0.0/24") + v4 := netip.MustParseAddr("100.64.0.1") node := types.Node{ ID: 0, MachineKey: machineKey.Public(), @@ -573,7 +552,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) { RequestTags: []string{"tag:exit"}, RoutableIPs: []netip.Prefix{defaultRouteV4, defaultRouteV6, route1, route2}, }, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, + IPv4: &v4, } db.DB.Save(&node) diff --git a/hscontrol/db/routes.go b/hscontrol/db/routes.go index a94e1a88b4..bc3f88a5bc 100644 --- a/hscontrol/db/routes.go +++ b/hscontrol/db/routes.go @@ -609,7 +609,7 @@ func EnableAutoApprovedRoutes( aclPolicy *policy.ACLPolicy, node *types.Node, ) error { - if len(node.IPAddresses) == 0 { + if node.IPv4 == nil && node.IPv6 == nil { return nil // This node has no IPAddresses, so can't possibly match any autoApprovers ACLs } @@ -652,7 +652,7 @@ func EnableAutoApprovedRoutes( } // approvedIPs should contain all of node's IPs if it matches the rule, so check for first - if approvedIps.Contains(node.IPAddresses[0]) { + if approvedIps.Contains(*node.IPv4) { approvedRoutes = append(approvedRoutes, advertisedRoute) } } diff --git a/hscontrol/grpcv1.go b/hscontrol/grpcv1.go index d5a1854ee7..5e52c978cb 100644 --- a/hscontrol/grpcv1.go +++ b/hscontrol/grpcv1.go @@ -195,7 +195,7 @@ func (api headscaleV1APIServer) RegisterNode( return nil, err } - addrs, err := api.h.ipAlloc.Next() + ipv4, ipv6, err := api.h.ipAlloc.Next() if err != nil { return nil, err } @@ -208,7 +208,7 @@ func (api headscaleV1APIServer) RegisterNode( request.GetUser(), nil, util.RegisterMethodCLI, - addrs, + ipv4, ipv6, ) }) if err != nil { diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index 93ab1f71ce..fe8af4d363 100644 --- a/hscontrol/mapper/mapper.go +++ b/hscontrol/mapper/mapper.go @@ -174,8 +174,8 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node) { "device_model": []string{node.Hostinfo.OS}, } - if len(node.IPAddresses) > 0 { - attrs.Add("device_ip", node.IPAddresses[0].String()) + if len(node.IPs()) > 0 { + attrs.Add("device_ip", node.IPs()[0].String()) } resolver.Addr = fmt.Sprintf("%s?%s", resolver.Addr, attrs.Encode()) diff --git a/hscontrol/mapper/mapper_test.go b/hscontrol/mapper/mapper_test.go index 3f4d6892e1..f62484700f 100644 --- a/hscontrol/mapper/mapper_test.go +++ b/hscontrol/mapper/mapper_test.go @@ -17,6 +17,11 @@ import ( "tailscale.com/types/key" ) +var iap = func(ipStr string) *netip.Addr { + ip := netip.MustParseAddr(ipStr) + return &ip +} + func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { mach := func(hostname, username string, userid uint) *types.Node { return &types.Node{ @@ -176,17 +181,17 @@ func Test_fullMapResponse(t *testing.T) { DiscoKey: mustDK( "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", ), - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "mini", - GivenName: "mini", - UserID: 0, - User: types.User{Name: "mini"}, - ForcedTags: []string{}, - AuthKeyID: 0, - AuthKey: &types.PreAuthKey{}, - LastSeen: &lastSeen, - Expiry: &expire, - Hostinfo: &tailcfg.Hostinfo{}, + IPv4: iap("100.64.0.1"), + Hostname: "mini", + GivenName: "mini", + UserID: 0, + User: types.User{Name: "mini"}, + ForcedTags: []string{}, + AuthKeyID: 0, + AuthKey: &types.PreAuthKey{}, + LastSeen: &lastSeen, + Expiry: &expire, + Hostinfo: &tailcfg.Hostinfo{}, Routes: []types.Route{ { Prefix: types.IPPrefix(netip.MustParsePrefix("0.0.0.0/0")), @@ -257,17 +262,17 @@ func Test_fullMapResponse(t *testing.T) { DiscoKey: mustDK( "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", ), - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "peer1", - GivenName: "peer1", - UserID: 0, - User: types.User{Name: "mini"}, - ForcedTags: []string{}, - LastSeen: &lastSeen, - Expiry: &expire, - Hostinfo: &tailcfg.Hostinfo{}, - Routes: []types.Route{}, - CreatedAt: created, + IPv4: iap("100.64.0.2"), + Hostname: "peer1", + GivenName: "peer1", + UserID: 0, + User: types.User{Name: "mini"}, + ForcedTags: []string{}, + LastSeen: &lastSeen, + Expiry: &expire, + Hostinfo: &tailcfg.Hostinfo{}, + Routes: []types.Route{}, + CreatedAt: created, } tailPeer1 := &tailcfg.Node{ @@ -312,17 +317,17 @@ func Test_fullMapResponse(t *testing.T) { DiscoKey: mustDK( "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", ), - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, - Hostname: "peer2", - GivenName: "peer2", - UserID: 1, - User: types.User{Name: "peer2"}, - ForcedTags: []string{}, - LastSeen: &lastSeen, - Expiry: &expire, - Hostinfo: &tailcfg.Hostinfo{}, - Routes: []types.Route{}, - CreatedAt: created, + IPv4: iap("100.64.0.3"), + Hostname: "peer2", + GivenName: "peer2", + UserID: 1, + User: types.User{Name: "peer2"}, + ForcedTags: []string{}, + LastSeen: &lastSeen, + Expiry: &expire, + Hostinfo: &tailcfg.Hostinfo{}, + Routes: []types.Route{}, + CreatedAt: created, } tests := []struct { diff --git a/hscontrol/mapper/tail.go b/hscontrol/mapper/tail.go index 97d12e862c..ac39d35e9e 100644 --- a/hscontrol/mapper/tail.go +++ b/hscontrol/mapper/tail.go @@ -44,7 +44,7 @@ func tailNode( pol *policy.ACLPolicy, cfg *types.Config, ) (*tailcfg.Node, error) { - addrs := node.IPAddresses.Prefixes() + addrs := node.Prefixes() allowedIPs := append( []netip.Prefix{}, diff --git a/hscontrol/mapper/tail_test.go b/hscontrol/mapper/tail_test.go index e79d9dc567..229f0f88c8 100644 --- a/hscontrol/mapper/tail_test.go +++ b/hscontrol/mapper/tail_test.go @@ -89,9 +89,7 @@ func TestTailNode(t *testing.T) { DiscoKey: mustDK( "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", ), - IPAddresses: []netip.Addr{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), Hostname: "mini", GivenName: "mini", UserID: 0, diff --git a/hscontrol/oidc.go b/hscontrol/oidc.go index 2ac1b56c11..0680ce2f8a 100644 --- a/hscontrol/oidc.go +++ b/hscontrol/oidc.go @@ -597,7 +597,7 @@ func (h *Headscale) registerNodeForOIDCCallback( machineKey *key.MachinePublic, expiry time.Time, ) error { - addrs, err := h.ipAlloc.Next() + ipv4, ipv6, err := h.ipAlloc.Next() if err != nil { return err } @@ -611,7 +611,7 @@ func (h *Headscale) registerNodeForOIDCCallback( user.Name, &expiry, util.RegisterMethodOIDC, - addrs, + ipv4, ipv6, ); err != nil { return err } diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index a4eee01e84..0f6158c659 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -229,7 +229,7 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F continue } - if node.IPAddresses.InIPSet(expanded) { + if node.InIPSet(expanded) { dests = append(dests, dest) } @@ -306,7 +306,7 @@ func (pol *ACLPolicy) CompileSSHPolicy( return nil, err } - if !node.IPAddresses.InIPSet(destSet) { + if !node.InIPSet(destSet) { continue } @@ -744,7 +744,7 @@ func (pol *ACLPolicy) expandIPsFromGroup( for _, user := range users { filteredNodes := filterNodesByUser(nodes, user) for _, node := range filteredNodes { - node.IPAddresses.AppendToIPSet(&build) + node.AppendToIPSet(&build) } } @@ -760,7 +760,7 @@ func (pol *ACLPolicy) expandIPsFromTag( // check for forced tags for _, node := range nodes { if util.StringOrPrefixListContains(node.ForcedTags, alias) { - node.IPAddresses.AppendToIPSet(&build) + node.AppendToIPSet(&build) } } @@ -792,7 +792,7 @@ func (pol *ACLPolicy) expandIPsFromTag( } if util.StringOrPrefixListContains(node.Hostinfo.RequestTags, alias) { - node.IPAddresses.AppendToIPSet(&build) + node.AppendToIPSet(&build) } } } @@ -815,7 +815,7 @@ func (pol *ACLPolicy) expandIPsFromUser( } for _, node := range filteredNodes { - node.IPAddresses.AppendToIPSet(&build) + node.AppendToIPSet(&build) } return build.IPSet() @@ -833,7 +833,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP( build.Add(ip) for _, node := range matches { - node.IPAddresses.AppendToIPSet(&build) + node.AppendToIPSet(&build) } return build.IPSet() @@ -850,11 +850,11 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix( // This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6 // addresses for the hosts that belong to tailscale. This doesnt really affect stuff like subnet routers. for _, node := range nodes { - for _, ip := range node.IPAddresses { + for _, ip := range node.IPs() { // log.Trace(). // Msgf("checking if node ip (%s) is part of prefix (%s): %v, is single ip prefix (%v), addr: %s", ip.String(), prefix.String(), prefix.Contains(ip), prefix.IsSingleIP(), prefix.Addr().String()) if prefix.Contains(ip) { - node.IPAddresses.AppendToIPSet(&build) + node.AppendToIPSet(&build) } } } diff --git a/hscontrol/policy/acls_test.go b/hscontrol/policy/acls_test.go index db1a0dd3b3..417ed1d1c5 100644 --- a/hscontrol/policy/acls_test.go +++ b/hscontrol/policy/acls_test.go @@ -16,6 +16,11 @@ import ( "tailscale.com/tailcfg" ) +var iap = func(ipStr string) *netip.Addr { + ip := netip.MustParseAddr(ipStr) + return &ip +} + func Test(t *testing.T) { check.TestingT(t) } @@ -387,14 +392,10 @@ acls: rules, err := pol.CompileFilterRules(types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.100.100.100"), - }, + IPv4: iap("100.100.100.100"), }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("200.200.200.200"), - }, + IPv4: iap("200.200.200.200"), User: types.User{ Name: "testuser", }, @@ -997,12 +998,10 @@ func Test_expandAlias(t *testing.T) { alias: "*", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, + IPv4: iap("100.64.0.1"), }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.78.84.227"), - }, + IPv4: iap("100.78.84.227"), }, }, }, @@ -1023,27 +1022,19 @@ func Test_expandAlias(t *testing.T) { alias: "group:accountant", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "mickael"}, }, }, @@ -1064,27 +1055,19 @@ func Test_expandAlias(t *testing.T) { alias: "group:hr", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "mickael"}, }, }, @@ -1129,9 +1112,7 @@ func Test_expandAlias(t *testing.T) { alias: "10.0.0.1", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("10.0.0.1"), - }, + IPv4: iap("10.0.0.1"), User: types.User{Name: "mickael"}, }, }, @@ -1150,10 +1131,8 @@ func Test_expandAlias(t *testing.T) { alias: "10.0.0.1", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("10.0.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), - }, + IPv4: iap("10.0.0.1"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), User: types.User{Name: "mickael"}, }, }, @@ -1172,10 +1151,8 @@ func Test_expandAlias(t *testing.T) { alias: "fd7a:115c:a1e0:ab12:4843:2222:6273:2222", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("10.0.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), - }, + IPv4: iap("10.0.0.1"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), User: types.User{Name: "mickael"}, }, }, @@ -1241,9 +1218,7 @@ func Test_expandAlias(t *testing.T) { alias: "tag:hr-webserver", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1252,9 +1227,7 @@ func Test_expandAlias(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1263,15 +1236,11 @@ func Test_expandAlias(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, }, }, @@ -1295,27 +1264,19 @@ func Test_expandAlias(t *testing.T) { alias: "tag:hr-webserver", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "mickael"}, }, }, @@ -1332,29 +1293,21 @@ func Test_expandAlias(t *testing.T) { alias: "tag:hr-webserver", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, ForcedTags: []string{"tag:hr-webserver"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, ForcedTags: []string{"tag:hr-webserver"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "mickael"}, }, }, @@ -1375,16 +1328,12 @@ func Test_expandAlias(t *testing.T) { alias: "tag:hr-webserver", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, ForcedTags: []string{"tag:hr-webserver"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1393,15 +1342,11 @@ func Test_expandAlias(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "mickael"}, }, }, @@ -1420,9 +1365,7 @@ func Test_expandAlias(t *testing.T) { alias: "joe", nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1431,9 +1374,7 @@ func Test_expandAlias(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1442,16 +1383,12 @@ func Test_expandAlias(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + IPv4: iap("100.64.0.3"), User: types.User{Name: "marc"}, Hostinfo: &tailcfg.Hostinfo{}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{}, }, @@ -1499,9 +1436,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1510,9 +1445,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1521,9 +1454,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{}, }, @@ -1532,9 +1463,9 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, want: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.4")}, - User: types.User{Name: "joe"}, - Hostinfo: &tailcfg.Hostinfo{}, + IPv4: iap("100.64.0.4"), + User: types.User{Name: "joe"}, + Hostinfo: &tailcfg.Hostinfo{}, }, }, }, @@ -1551,9 +1482,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1562,9 +1491,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1573,9 +1500,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{}, }, @@ -1584,9 +1509,9 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, want: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.4")}, - User: types.User{Name: "joe"}, - Hostinfo: &tailcfg.Hostinfo{}, + IPv4: iap("100.64.0.4"), + User: types.User{Name: "joe"}, + Hostinfo: &tailcfg.Hostinfo{}, }, }, }, @@ -1598,9 +1523,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1609,17 +1532,13 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, ForcedTags: []string{"tag:accountant-webserver"}, Hostinfo: &tailcfg.Hostinfo{}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{}, }, @@ -1628,9 +1547,9 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, want: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.4")}, - User: types.User{Name: "joe"}, - Hostinfo: &tailcfg.Hostinfo{}, + IPv4: iap("100.64.0.4"), + User: types.User{Name: "joe"}, + Hostinfo: &tailcfg.Hostinfo{}, }, }, }, @@ -1642,9 +1561,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1653,9 +1570,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1664,9 +1579,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{}, }, @@ -1675,9 +1588,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, want: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1686,9 +1597,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + IPv4: iap("100.64.0.2"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{ OS: "centos", @@ -1697,9 +1606,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { }, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - }, + IPv4: iap("100.64.0.4"), User: types.User{Name: "joe"}, Hostinfo: &tailcfg.Hostinfo{}, }, @@ -1757,10 +1664,8 @@ func TestACLPolicy_generateFilterRules(t *testing.T) { args: args{ nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2221"), - }, + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2221"), }, }, }, @@ -1803,17 +1708,13 @@ func TestACLPolicy_generateFilterRules(t *testing.T) { args: args{ nodes: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2221"), - }, + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2221"), User: types.User{Name: "mickael"}, }, &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), - }, + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), User: types.User{Name: "mickael"}, }, }, @@ -1884,18 +1785,14 @@ func TestReduceFilterRules(t *testing.T) { }, }, node: &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2221"), - }, + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2221"), User: types.User{Name: "mickael"}, }, peers: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), - }, + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"), User: types.User{Name: "mickael"}, }, }, @@ -1921,10 +1818,8 @@ func TestReduceFilterRules(t *testing.T) { }, }, node: &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0::1"), - }, + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), User: types.User{Name: "user1"}, Hostinfo: &tailcfg.Hostinfo{ RoutableIPs: []netip.Prefix{ @@ -1934,10 +1829,8 @@ func TestReduceFilterRules(t *testing.T) { }, peers: types.Nodes{ &types.Node{ - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - netip.MustParseAddr("fd7a:115c:a1e0::2"), - }, + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), User: types.User{Name: "user1"}, }, }, @@ -2153,24 +2046,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2183,21 +2070,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ // current nodes - ID: 1, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - User: types.User{Name: "joe"}, + ID: 1, + IPv4: iap("100.64.0.1"), + User: types.User{Name: "joe"}, }, }, want: types.Nodes{ &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.2")}, - User: types.User{Name: "marc"}, + ID: 2, + IPv4: iap("100.64.0.2"), + User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.3")}, - User: types.User{Name: "mickael"}, + ID: 3, + IPv4: iap("100.64.0.3"), + User: types.User{Name: "mickael"}, }, }, }, @@ -2206,24 +2093,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2236,16 +2117,16 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ // current nodes - ID: 1, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - User: types.User{Name: "joe"}, + ID: 1, + IPv4: iap("100.64.0.1"), + User: types.User{Name: "joe"}, }, }, want: types.Nodes{ &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.2")}, - User: types.User{Name: "marc"}, + ID: 2, + IPv4: iap("100.64.0.2"), + User: types.User{Name: "marc"}, }, }, }, @@ -2254,24 +2135,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2284,16 +2159,16 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ // current nodes - ID: 2, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.2")}, - User: types.User{Name: "marc"}, + ID: 2, + IPv4: iap("100.64.0.2"), + User: types.User{Name: "marc"}, }, }, want: types.Nodes{ &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.3")}, - User: types.User{Name: "mickael"}, + ID: 3, + IPv4: iap("100.64.0.3"), + User: types.User{Name: "mickael"}, }, }, }, @@ -2302,24 +2177,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2332,19 +2201,15 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ // current nodes - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, }, want: types.Nodes{ &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, }, @@ -2354,24 +2219,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2384,26 +2243,20 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ // current nodes - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, }, want: types.Nodes{ &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2413,24 +2266,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, @@ -2443,23 +2290,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ // current nodes - ID: 2, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.2")}, - User: types.User{Name: "marc"}, + ID: 2, + IPv4: iap("100.64.0.2"), + User: types.User{Name: "marc"}, }, }, want: types.Nodes{ &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.3")}, - User: types.User{Name: "mickael"}, + ID: 3, + IPv4: iap("100.64.0.3"), + User: types.User{Name: "mickael"}, }, }, }, @@ -2468,33 +2313,27 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: types.Nodes{ // list of all nodess in the database &types.Node{ - ID: 1, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - }, + ID: 1, + IPv4: iap("100.64.0.1"), User: types.User{Name: "joe"}, }, &types.Node{ - ID: 2, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - }, + ID: 2, + IPv4: iap("100.64.0.2"), User: types.User{Name: "marc"}, }, &types.Node{ - ID: 3, - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - }, + ID: 3, + IPv4: iap("100.64.0.3"), User: types.User{Name: "mickael"}, }, }, rules: []tailcfg.FilterRule{ // list of all ACLRules registered }, node: &types.Node{ // current nodes - ID: 2, - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.2")}, - User: types.User{Name: "marc"}, + ID: 2, + IPv4: iap("100.64.0.2"), + User: types.User{Name: "marc"}, }, }, want: types.Nodes{}, @@ -2510,38 +2349,30 @@ func Test_getFilteredByACLPeers(t *testing.T) { &types.Node{ ID: 1, Hostname: "ts-head-upcrmb", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - netip.MustParseAddr("fd7a:115c:a1e0::3"), - }, - User: types.User{Name: "user1"}, + IPv4: iap("100.64.0.3"), + IPv6: iap("fd7a:115c:a1e0::3"), + User: types.User{Name: "user1"}, }, &types.Node{ ID: 2, Hostname: "ts-unstable-rlwpvr", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - netip.MustParseAddr("fd7a:115c:a1e0::4"), - }, - User: types.User{Name: "user1"}, + IPv4: iap("100.64.0.4"), + IPv6: iap("fd7a:115c:a1e0::4"), + User: types.User{Name: "user1"}, }, &types.Node{ ID: 3, Hostname: "ts-head-8w6paa", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0::1"), - }, - User: types.User{Name: "user2"}, + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user2"}, }, &types.Node{ ID: 4, Hostname: "ts-unstable-lys2ib", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.2"), - netip.MustParseAddr("fd7a:115c:a1e0::2"), - }, - User: types.User{Name: "user2"}, + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), + User: types.User{Name: "user2"}, }, }, rules: []tailcfg.FilterRule{ // list of all ACLRules registered @@ -2561,31 +2392,25 @@ func Test_getFilteredByACLPeers(t *testing.T) { node: &types.Node{ // current nodes ID: 3, Hostname: "ts-head-8w6paa", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.1"), - netip.MustParseAddr("fd7a:115c:a1e0::1"), - }, - User: types.User{Name: "user2"}, + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user2"}, }, }, want: types.Nodes{ &types.Node{ ID: 1, Hostname: "ts-head-upcrmb", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.3"), - netip.MustParseAddr("fd7a:115c:a1e0::3"), - }, - User: types.User{Name: "user1"}, + IPv4: iap("100.64.0.3"), + IPv6: iap("fd7a:115c:a1e0::3"), + User: types.User{Name: "user1"}, }, &types.Node{ ID: 2, Hostname: "ts-unstable-rlwpvr", - IPAddresses: types.NodeAddresses{ - netip.MustParseAddr("100.64.0.4"), - netip.MustParseAddr("fd7a:115c:a1e0::4"), - }, - User: types.User{Name: "user1"}, + IPv4: iap("100.64.0.4"), + IPv6: iap("fd7a:115c:a1e0::4"), + User: types.User{Name: "user1"}, }, }, }, @@ -2594,16 +2419,16 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: []*types.Node{ { - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "peer1", - User: types.User{Name: "mini"}, + ID: 1, + IPv4: iap("100.64.0.2"), + Hostname: "peer1", + User: types.User{Name: "mini"}, }, { - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, - Hostname: "peer2", - User: types.User{Name: "peer2"}, + ID: 2, + IPv4: iap("100.64.0.3"), + Hostname: "peer2", + User: types.User{Name: "peer2"}, }, }, rules: []tailcfg.FilterRule{ @@ -2616,18 +2441,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ - ID: 0, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "mini", - User: types.User{Name: "mini"}, + ID: 0, + IPv4: iap("100.64.0.1"), + Hostname: "mini", + User: types.User{Name: "mini"}, }, }, want: []*types.Node{ { - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, - Hostname: "peer2", - User: types.User{Name: "peer2"}, + ID: 2, + IPv4: iap("100.64.0.3"), + Hostname: "peer2", + User: types.User{Name: "peer2"}, }, }, }, @@ -2636,22 +2461,22 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: []*types.Node{ { - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "user1-2", - User: types.User{Name: "user1"}, + ID: 1, + IPv4: iap("100.64.0.2"), + Hostname: "user1-2", + User: types.User{Name: "user1"}, }, { - ID: 0, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "user1-1", - User: types.User{Name: "user1"}, + ID: 0, + IPv4: iap("100.64.0.1"), + Hostname: "user1-1", + User: types.User{Name: "user1"}, }, { - ID: 3, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")}, - Hostname: "user2-2", - User: types.User{Name: "user2"}, + ID: 3, + IPv4: iap("100.64.0.4"), + Hostname: "user2-2", + User: types.User{Name: "user2"}, }, }, rules: []tailcfg.FilterRule{ @@ -2685,30 +2510,30 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, - Hostname: "user-2-1", - User: types.User{Name: "user2"}, + ID: 2, + IPv4: iap("100.64.0.3"), + Hostname: "user-2-1", + User: types.User{Name: "user2"}, }, }, want: []*types.Node{ { - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "user1-2", - User: types.User{Name: "user1"}, + ID: 1, + IPv4: iap("100.64.0.2"), + Hostname: "user1-2", + User: types.User{Name: "user1"}, }, { - ID: 0, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "user1-1", - User: types.User{Name: "user1"}, + ID: 0, + IPv4: iap("100.64.0.1"), + Hostname: "user1-1", + User: types.User{Name: "user1"}, }, { - ID: 3, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")}, - Hostname: "user2-2", - User: types.User{Name: "user2"}, + ID: 3, + IPv4: iap("100.64.0.4"), + Hostname: "user2-2", + User: types.User{Name: "user2"}, }, }, }, @@ -2717,22 +2542,22 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: []*types.Node{ { - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "user1-2", - User: types.User{Name: "user1"}, + ID: 1, + IPv4: iap("100.64.0.2"), + Hostname: "user1-2", + User: types.User{Name: "user1"}, }, { - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, - Hostname: "user-2-1", - User: types.User{Name: "user2"}, + ID: 2, + IPv4: iap("100.64.0.3"), + Hostname: "user-2-1", + User: types.User{Name: "user2"}, }, { - ID: 3, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")}, - Hostname: "user2-2", - User: types.User{Name: "user2"}, + ID: 3, + IPv4: iap("100.64.0.4"), + Hostname: "user2-2", + User: types.User{Name: "user2"}, }, }, rules: []tailcfg.FilterRule{ @@ -2766,30 +2591,30 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ - ID: 0, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "user1-1", - User: types.User{Name: "user1"}, + ID: 0, + IPv4: iap("100.64.0.1"), + Hostname: "user1-1", + User: types.User{Name: "user1"}, }, }, want: []*types.Node{ { - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "user1-2", - User: types.User{Name: "user1"}, + ID: 1, + IPv4: iap("100.64.0.2"), + Hostname: "user1-2", + User: types.User{Name: "user1"}, }, { - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, - Hostname: "user-2-1", - User: types.User{Name: "user2"}, + ID: 2, + IPv4: iap("100.64.0.3"), + Hostname: "user-2-1", + User: types.User{Name: "user2"}, }, { - ID: 3, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")}, - Hostname: "user2-2", - User: types.User{Name: "user2"}, + ID: 3, + IPv4: iap("100.64.0.4"), + Hostname: "user2-2", + User: types.User{Name: "user2"}, }, }, }, @@ -2799,16 +2624,16 @@ func Test_getFilteredByACLPeers(t *testing.T) { args: args{ nodes: []*types.Node{ { - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "user1", - User: types.User{Name: "user1"}, + ID: 1, + IPv4: iap("100.64.0.1"), + Hostname: "user1", + User: types.User{Name: "user1"}, }, { - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "router", - User: types.User{Name: "router"}, + ID: 2, + IPv4: iap("100.64.0.2"), + Hostname: "router", + User: types.User{Name: "router"}, Routes: types.Routes{ types.Route{ NodeID: 2, @@ -2830,18 +2655,18 @@ func Test_getFilteredByACLPeers(t *testing.T) { }, }, node: &types.Node{ - ID: 1, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, - Hostname: "user1", - User: types.User{Name: "user1"}, + ID: 1, + IPv4: iap("100.64.0.1"), + Hostname: "user1", + User: types.User{Name: "user1"}, }, }, want: []*types.Node{ { - ID: 2, - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, - Hostname: "router", - User: types.User{Name: "router"}, + ID: 2, + IPv4: iap("100.64.0.2"), + Hostname: "router", + User: types.User{Name: "router"}, Routes: types.Routes{ types.Route{ NodeID: 2, @@ -2887,18 +2712,18 @@ func TestSSHRules(t *testing.T) { { name: "peers-can-connect", node: types.Node{ - Hostname: "testnodes", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.99.42")}, - UserID: 0, + Hostname: "testnodes", + IPv4: iap("100.64.99.42"), + UserID: 0, User: types.User{ Name: "user1", }, }, peers: types.Nodes{ &types.Node{ - Hostname: "testnodes2", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - UserID: 0, + Hostname: "testnodes2", + IPv4: iap("100.64.0.1"), + UserID: 0, User: types.User{ Name: "user1", }, @@ -2995,18 +2820,18 @@ func TestSSHRules(t *testing.T) { { name: "peers-cannot-connect", node: types.Node{ - Hostname: "testnodes", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - UserID: 0, + Hostname: "testnodes", + IPv4: iap("100.64.0.1"), + UserID: 0, User: types.User{ Name: "user1", }, }, peers: types.Nodes{ &types.Node{ - Hostname: "testnodes2", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.99.42")}, - UserID: 0, + Hostname: "testnodes2", + IPv4: iap("100.64.99.42"), + UserID: 0, User: types.User{ Name: "user1", }, @@ -3131,10 +2956,10 @@ func TestValidExpandTagOwnersInSources(t *testing.T) { } node := &types.Node{ - ID: 0, - Hostname: "testnodes", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - UserID: 0, + ID: 0, + Hostname: "testnodes", + IPv4: iap("100.64.0.1"), + UserID: 0, User: types.User{ Name: "user1", }, @@ -3183,10 +3008,10 @@ func TestInvalidTagValidUser(t *testing.T) { } node := &types.Node{ - ID: 1, - Hostname: "testnodes", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - UserID: 1, + ID: 1, + Hostname: "testnodes", + IPv4: iap("100.64.0.1"), + UserID: 1, User: types.User{ Name: "user1", }, @@ -3234,10 +3059,10 @@ func TestValidExpandTagOwnersInDestinations(t *testing.T) { } node := &types.Node{ - ID: 1, - Hostname: "testnodes", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - UserID: 1, + ID: 1, + Hostname: "testnodes", + IPv4: iap("100.64.0.1"), + UserID: 1, User: types.User{ Name: "user1", }, @@ -3295,10 +3120,10 @@ func TestValidTagInvalidUser(t *testing.T) { } node := &types.Node{ - ID: 1, - Hostname: "webserver", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.1")}, - UserID: 1, + ID: 1, + Hostname: "webserver", + IPv4: iap("100.64.0.1"), + UserID: 1, User: types.User{ Name: "user1", }, @@ -3312,10 +3137,10 @@ func TestValidTagInvalidUser(t *testing.T) { } nodes2 := &types.Node{ - ID: 2, - Hostname: "user", - IPAddresses: types.NodeAddresses{netip.MustParseAddr("100.64.0.2")}, - UserID: 1, + ID: 2, + Hostname: "user", + IPv4: iap("100.64.0.2"), + UserID: 1, User: types.User{ Name: "user1", }, diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index 2d6c6310da..0e30bd9e81 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -1,12 +1,11 @@ package types import ( - "database/sql/driver" + "database/sql" "encoding/json" "errors" "fmt" "net/netip" - "sort" "strconv" "strings" "time" @@ -14,7 +13,6 @@ import ( v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/policy/matcher" "github.com/juanfont/headscale/hscontrol/util" - "github.com/rs/zerolog/log" "go4.org/netipx" "google.golang.org/protobuf/types/known/timestamppb" "gorm.io/gorm" @@ -83,7 +81,19 @@ type Node struct { HostinfoDatabaseField string `gorm:"column:host_info"` Hostinfo *tailcfg.Hostinfo `gorm:"-"` - IPAddresses NodeAddresses + // IPv4DatabaseField is the string representation of v4 address, + // it is _only_ used for reading and writing the key to the + // database and should not be used. + // Use V4 instead. + IPv4DatabaseField sql.NullString `gorm:"column:ipv4"` + IPv4 *netip.Addr `gorm:"-"` + + // IPv6DatabaseField is the string representation of v4 address, + // it is _only_ used for reading and writing the key to the + // database and should not be used. + // Use V6 instead. + IPv6DatabaseField sql.NullString `gorm:"column:ipv6"` + IPv6 *netip.Addr `gorm:"-"` // Hostname represents the name given by the Tailscale // client during registration @@ -123,34 +133,41 @@ type ( Nodes []*Node ) -type NodeAddresses []netip.Addr +// IsExpired returns whether the node registration has expired. +func (node Node) IsExpired() bool { + // If Expiry is not set, the client has not indicated that + // it wants an expiry time, it is therefor considered + // to mean "not expired" + if node.Expiry == nil || node.Expiry.IsZero() { + return false + } -func (na NodeAddresses) Sort() { - sort.Slice(na, func(index1, index2 int) bool { - if na[index1].Is4() && na[index2].Is6() { - return true - } - if na[index1].Is6() && na[index2].Is4() { - return false - } + return time.Since(*node.Expiry) > 0 +} - return na[index1].Compare(na[index2]) < 0 - }) +// IsEphemeral returns if the node is registered as an Ephemeral node. +// https://tailscale.com/kb/1111/ephemeral-nodes/ +func (node *Node) IsEphemeral() bool { + return node.AuthKey != nil && node.AuthKey.Ephemeral } -func (na NodeAddresses) StringSlice() []string { - na.Sort() - strSlice := make([]string, 0, len(na)) - for _, addr := range na { - strSlice = append(strSlice, addr.String()) +func (node *Node) IPs() []netip.Addr { + var ret []netip.Addr + + if node.IPv4 != nil { + ret = append(ret, *node.IPv4) + } + + if node.IPv6 != nil { + ret = append(ret, *node.IPv6) } - return strSlice + return ret } -func (na NodeAddresses) Prefixes() []netip.Prefix { +func (node *Node) Prefixes() []netip.Prefix { addrs := []netip.Prefix{} - for _, nodeAddress := range na { + for _, nodeAddress := range node.IPs() { ip := netip.PrefixFrom(nodeAddress, nodeAddress.BitLen()) addrs = append(addrs, ip) } @@ -158,8 +175,22 @@ func (na NodeAddresses) Prefixes() []netip.Prefix { return addrs } -func (na NodeAddresses) InIPSet(set *netipx.IPSet) bool { - for _, nodeAddr := range na { +func (node *Node) IPsAsString() []string { + var ret []string + + if node.IPv4 != nil { + ret = append(ret, node.IPv4.String()) + } + + if node.IPv6 != nil { + ret = append(ret, node.IPv6.String()) + } + + return ret +} + +func (node *Node) InIPSet(set *netipx.IPSet) bool { + for _, nodeAddr := range node.IPs() { if set.Contains(nodeAddr) { return true } @@ -170,62 +201,15 @@ func (na NodeAddresses) InIPSet(set *netipx.IPSet) bool { // AppendToIPSet adds the individual ips in NodeAddresses to a // given netipx.IPSetBuilder. -func (na NodeAddresses) AppendToIPSet(build *netipx.IPSetBuilder) { - for _, ip := range na { +func (node *Node) AppendToIPSet(build *netipx.IPSetBuilder) { + for _, ip := range node.IPs() { build.Add(ip) } } -func (na *NodeAddresses) Scan(destination interface{}) error { - switch value := destination.(type) { - case string: - addresses := strings.Split(value, ",") - *na = (*na)[:0] - for _, addr := range addresses { - if len(addr) < 1 { - continue - } - parsed, err := netip.ParseAddr(addr) - if err != nil { - return err - } - *na = append(*na, parsed) - } - - return nil - - default: - return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination) - } -} - -// Value return json value, implement driver.Valuer interface. -func (na NodeAddresses) Value() (driver.Value, error) { - addresses := strings.Join(na.StringSlice(), ",") - - return addresses, nil -} - -// IsExpired returns whether the node registration has expired. -func (node Node) IsExpired() bool { - // If Expiry is not set, the client has not indicated that - // it wants an expiry time, it is therefor considered - // to mean "not expired" - if node.Expiry == nil || node.Expiry.IsZero() { - return false - } - - return time.Since(*node.Expiry) > 0 -} - -// IsEphemeral returns if the node is registered as an Ephemeral node. -// https://tailscale.com/kb/1111/ephemeral-nodes/ -func (node *Node) IsEphemeral() bool { - return node.AuthKey != nil && node.AuthKey.Ephemeral -} - func (node *Node) CanAccess(filter []tailcfg.FilterRule, node2 *Node) bool { - allowedIPs := append([]netip.Addr{}, node2.IPAddresses...) + src := node.IPs() + allowedIPs := node2.IPs() for _, route := range node2.Routes { if route.Enabled { @@ -237,7 +221,7 @@ func (node *Node) CanAccess(filter []tailcfg.FilterRule, node2 *Node) bool { // TODO(kradalby): Cache or pregen this matcher := matcher.MatchFromFilterRule(rule) - if !matcher.SrcsContainsIPs([]netip.Addr(node.IPAddresses)) { + if !matcher.SrcsContainsIPs(src) { continue } @@ -250,13 +234,16 @@ func (node *Node) CanAccess(filter []tailcfg.FilterRule, node2 *Node) bool { } func (nodes Nodes) FilterByIP(ip netip.Addr) Nodes { - found := make(Nodes, 0) + var found Nodes for _, node := range nodes { - for _, mIP := range node.IPAddresses { - if ip == mIP { - found = append(found, node) - } + if node.IPv4 != nil && ip == *node.IPv4 { + found = append(found, node) + continue + } + + if node.IPv6 != nil && ip == *node.IPv6 { + found = append(found, node) } } @@ -281,10 +268,22 @@ func (node *Node) BeforeSave(tx *gorm.DB) error { hi, err := json.Marshal(node.Hostinfo) if err != nil { - return fmt.Errorf("failed to marshal Hostinfo to store in db: %w", err) + return fmt.Errorf("marshalling Hostinfo to store in db: %w", err) } node.HostinfoDatabaseField = string(hi) + if node.IPv4 != nil { + node.IPv4DatabaseField.String, node.IPv4DatabaseField.Valid = node.IPv4.String(), true + } else { + node.IPv4DatabaseField.String, node.IPv4DatabaseField.Valid = "", false + } + + if node.IPv6 != nil { + node.IPv6DatabaseField.String, node.IPv6DatabaseField.Valid = node.IPv6.String(), true + } else { + node.IPv6DatabaseField.String, node.IPv6DatabaseField.Valid = "", false + } + return nil } @@ -296,19 +295,19 @@ func (node *Node) BeforeSave(tx *gorm.DB) error { func (node *Node) AfterFind(tx *gorm.DB) error { var machineKey key.MachinePublic if err := machineKey.UnmarshalText([]byte(node.MachineKeyDatabaseField)); err != nil { - return fmt.Errorf("failed to unmarshal machine key from db: %w", err) + return fmt.Errorf("unmarshalling machine key from db: %w", err) } node.MachineKey = machineKey var nodeKey key.NodePublic if err := nodeKey.UnmarshalText([]byte(node.NodeKeyDatabaseField)); err != nil { - return fmt.Errorf("failed to unmarshal node key from db: %w", err) + return fmt.Errorf("unmarshalling node key from db: %w", err) } node.NodeKey = nodeKey var discoKey key.DiscoPublic if err := discoKey.UnmarshalText([]byte(node.DiscoKeyDatabaseField)); err != nil { - return fmt.Errorf("failed to unmarshal disco key from db: %w", err) + return fmt.Errorf("unmarshalling disco key from db: %w", err) } node.DiscoKey = discoKey @@ -316,7 +315,7 @@ func (node *Node) AfterFind(tx *gorm.DB) error { for idx, ep := range node.EndpointsDatabaseField { addrPort, err := netip.ParseAddrPort(ep) if err != nil { - return fmt.Errorf("failed to parse endpoint from db: %w", err) + return fmt.Errorf("parsing endpoint from db: %w", err) } endpoints[idx] = addrPort @@ -325,12 +324,28 @@ func (node *Node) AfterFind(tx *gorm.DB) error { var hi tailcfg.Hostinfo if err := json.Unmarshal([]byte(node.HostinfoDatabaseField), &hi); err != nil { - log.Trace().Err(err).Msgf("Hostinfo content: %s", node.HostinfoDatabaseField) - - return fmt.Errorf("failed to unmarshal Hostinfo from db: %w", err) + return fmt.Errorf("unmarshalling hostinfo from database: %w", err) } node.Hostinfo = &hi + if node.IPv4DatabaseField.Valid { + ip, err := netip.ParseAddr(node.IPv4DatabaseField.String) + if err != nil { + return fmt.Errorf("parsing IPv4 from database: %w", err) + } + + node.IPv4 = &ip + } + + if node.IPv6DatabaseField.Valid { + ip, err := netip.ParseAddr(node.IPv6DatabaseField.String) + if err != nil { + return fmt.Errorf("parsing IPv6 from database: %w", err) + } + + node.IPv6 = &ip + } + return nil } @@ -339,9 +354,11 @@ func (node *Node) Proto() *v1.Node { Id: uint64(node.ID), MachineKey: node.MachineKey.String(), - NodeKey: node.NodeKey.String(), - DiscoKey: node.DiscoKey.String(), - IpAddresses: node.IPAddresses.StringSlice(), + NodeKey: node.NodeKey.String(), + DiscoKey: node.DiscoKey.String(), + + // TODO(kradalby): replace list with v4, v6 field? + IpAddresses: node.IPsAsString(), Name: node.Hostname, GivenName: node.GivenName, User: node.User.Proto(), diff --git a/hscontrol/types/node_test.go b/hscontrol/types/node_test.go index 712a839e75..157be89e00 100644 --- a/hscontrol/types/node_test.go +++ b/hscontrol/types/node_test.go @@ -12,6 +12,10 @@ import ( ) func Test_NodeCanAccess(t *testing.T) { + iap := func(ipStr string) *netip.Addr { + ip := netip.MustParseAddr(ipStr) + return &ip + } tests := []struct { name string node1 Node @@ -22,10 +26,10 @@ func Test_NodeCanAccess(t *testing.T) { { name: "no-rules", node1: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, + IPv4: iap("10.0.0.1"), }, node2: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, + IPv4: iap("10.0.0.2"), }, rules: []tailcfg.FilterRule{}, want: false, @@ -33,10 +37,10 @@ func Test_NodeCanAccess(t *testing.T) { { name: "wildcard", node1: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, + IPv4: iap("10.0.0.1"), }, node2: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, + IPv4: iap("10.0.0.2"), }, rules: []tailcfg.FilterRule{ { @@ -54,10 +58,10 @@ func Test_NodeCanAccess(t *testing.T) { { name: "other-cant-access-src", node1: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, + IPv4: iap("100.64.0.1"), }, node2: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, + IPv4: iap("100.64.0.3"), }, rules: []tailcfg.FilterRule{ { @@ -72,10 +76,10 @@ func Test_NodeCanAccess(t *testing.T) { { name: "dest-cant-access-src", node1: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, + IPv4: iap("100.64.0.3"), }, node2: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, + IPv4: iap("100.64.0.2"), }, rules: []tailcfg.FilterRule{ { @@ -90,10 +94,10 @@ func Test_NodeCanAccess(t *testing.T) { { name: "src-can-access-dest", node1: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, + IPv4: iap("100.64.0.2"), }, node2: Node{ - IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, + IPv4: iap("100.64.0.3"), }, rules: []tailcfg.FilterRule{ { @@ -118,32 +122,6 @@ func Test_NodeCanAccess(t *testing.T) { } } -func TestNodeAddressesOrder(t *testing.T) { - machineAddresses := NodeAddresses{ - netip.MustParseAddr("2001:db8::2"), - netip.MustParseAddr("100.64.0.2"), - netip.MustParseAddr("2001:db8::1"), - netip.MustParseAddr("100.64.0.1"), - } - - strSlice := machineAddresses.StringSlice() - expected := []string{ - "100.64.0.1", - "100.64.0.2", - "2001:db8::1", - "2001:db8::2", - } - - if len(strSlice) != len(expected) { - t.Fatalf("unexpected slice length: got %v, want %v", len(strSlice), len(expected)) - } - for i, addr := range strSlice { - if addr != expected[i] { - t.Errorf("unexpected address at index %v: got %v, want %v", i, addr, expected[i]) - } - } -} - func TestNodeFQDN(t *testing.T) { tests := []struct { name string diff --git a/hscontrol/util/dns_test.go b/hscontrol/util/dns_test.go index 9d9b08b3c0..2559cae68d 100644 --- a/hscontrol/util/dns_test.go +++ b/hscontrol/util/dns_test.go @@ -148,10 +148,7 @@ func TestCheckForFQDNRules(t *testing.T) { } func TestMagicDNSRootDomains100(t *testing.T) { - prefixes := []netip.Prefix{ - netip.MustParsePrefix("100.64.0.0/10"), - } - domains := GenerateMagicDNSRootDomains(prefixes) + domains := GenerateIPv4DNSRootDomain(netip.MustParsePrefix("100.64.0.0/10")) found := false for _, domain := range domains { @@ -185,10 +182,7 @@ func TestMagicDNSRootDomains100(t *testing.T) { } func TestMagicDNSRootDomains172(t *testing.T) { - prefixes := []netip.Prefix{ - netip.MustParsePrefix("172.16.0.0/16"), - } - domains := GenerateMagicDNSRootDomains(prefixes) + domains := GenerateIPv4DNSRootDomain(netip.MustParsePrefix("172.16.0.0/16")) found := false for _, domain := range domains { @@ -213,20 +207,14 @@ func TestMagicDNSRootDomains172(t *testing.T) { // Happens when netmask is a multiple of 4 bits (sounds likely). func TestMagicDNSRootDomainsIPv6Single(t *testing.T) { - prefixes := []netip.Prefix{ - netip.MustParsePrefix("fd7a:115c:a1e0::/48"), - } - domains := GenerateMagicDNSRootDomains(prefixes) + domains := GenerateIPv6DNSRootDomain(netip.MustParsePrefix("fd7a:115c:a1e0::/48")) assert.Len(t, domains, 1) assert.Equal(t, "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.", domains[0].WithTrailingDot()) } func TestMagicDNSRootDomainsIPv6SingleMultiple(t *testing.T) { - prefixes := []netip.Prefix{ - netip.MustParsePrefix("fd7a:115c:a1e0::/50"), - } - domains := GenerateMagicDNSRootDomains(prefixes) + domains := GenerateIPv6DNSRootDomain(netip.MustParsePrefix("fd7a:115c:a1e0::/50")) yieldsRoot := func(dom string) bool { for _, candidate := range domains {