From 3964dec1c638ac85755ee105b1be5c2db45b42c0 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 2 Oct 2024 09:06:09 +0200 Subject: [PATCH] use tsaddr library and cleanups (#2150) * resuse tsaddr code instead of handrolled Signed-off-by: Kristoffer Dalby * ensure we dont give out internal tailscale IPs Signed-off-by: Kristoffer Dalby * use prefix instead of string for routes Signed-off-by: Kristoffer Dalby * remove old custom compare func Signed-off-by: Kristoffer Dalby * trim unused util code Signed-off-by: Kristoffer Dalby --------- Signed-off-by: Kristoffer Dalby --- cmd/headscale/cli/routes.go | 4 ++-- flake.nix | 2 +- go.mod | 11 +++++------ go.sum | 32 ++++++++++++++++++-------------- hscontrol/db/ip.go | 12 ++++++++++-- hscontrol/db/ip_test.go | 26 ++++++++++++++++++++++++++ hscontrol/db/node.go | 27 ++++++--------------------- hscontrol/db/node_test.go | 16 +++++++--------- hscontrol/db/routes.go | 7 ++++--- hscontrol/db/routes_test.go | 26 +++++++++++++++----------- hscontrol/mapper/mapper_test.go | 5 +++-- hscontrol/mapper/tail_test.go | 5 +++-- hscontrol/policy/acls.go | 16 +++++++++------- hscontrol/policy/acls_test.go | 14 ++++++++------ hscontrol/poll.go | 11 +++-------- hscontrol/types/routes.go | 8 ++------ hscontrol/util/addr.go | 13 +------------ hscontrol/util/key.go | 23 ----------------------- hscontrol/util/net.go | 18 ------------------ 19 files changed, 123 insertions(+), 153 deletions(-) diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go index 96227b31..dfbcb8fa 100644 --- a/cmd/headscale/cli/routes.go +++ b/cmd/headscale/cli/routes.go @@ -7,10 +7,10 @@ import ( "strconv" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" - "github.com/juanfont/headscale/hscontrol/types" "github.com/pterm/pterm" "github.com/spf13/cobra" "google.golang.org/grpc/status" + "tailscale.com/net/tsaddr" ) const ( @@ -245,7 +245,7 @@ func routesToPtables(routes []*v1.Route) pterm.TableData { continue } - if prefix == types.ExitRouteV4 || prefix == types.ExitRouteV6 { + if tsaddr.IsExitRoute(prefix) { isPrimaryStr = "-" } else { isPrimaryStr = strconv.FormatBool(route.GetIsPrimary()) diff --git a/flake.nix b/flake.nix index 79dd58e8..a4b87584 100644 --- a/flake.nix +++ b/flake.nix @@ -32,7 +32,7 @@ # When updating go.mod or go.sum, a new sha will need to be calculated, # update this if you have a mismatch after doing a change to thos files. - vendorHash = "sha256-+8dOxPG/Q+wuHgRwwWqdphHOuop0W9dVyClyQuh7aRc="; + vendorHash = "sha256-/CPUkLLCwNKK3z3UZyF+AY0ArMnLaDmH0HV3/RYHo4c="; subPackages = ["cmd/headscale"]; diff --git a/go.mod b/go.mod index 73893d82..2b4a27f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/juanfont/headscale -go 1.23.0 +go 1.23.1 require ( github.com/AlecAivazis/survey/v2 v2.3.7 @@ -48,7 +48,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.11 - tailscale.com v1.72.1 + tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7 ) require ( @@ -118,7 +118,7 @@ require ( github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify v1.0.1 // indirect + github.com/illarion/gonotify/v2 v2.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -175,15 +175,14 @@ require ( github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect - github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect + github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257 // indirect github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185 // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect - github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 // indirect + github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc // indirect github.com/tcnksm/go-httpstat v0.2.0 // indirect github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect - github.com/vishvananda/netlink v1.2.1-beta.2 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect diff --git a/go.sum b/go.sum index 2213f423..6536e1d3 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1r github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= @@ -252,8 +252,8 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= +github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= +github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 h1:kD8PseueGeYiid/Mmcv17Q0Qqicc4F46jcX22L/e/Hs= @@ -472,8 +472,8 @@ github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPx github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257 h1:6WsbDYsikRNmmbfZoRoyIEA9tfl0aspPAE0t7nBj2B4= @@ -486,8 +486,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:t github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= +github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc= +github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= @@ -502,8 +502,6 @@ github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= @@ -699,8 +697,8 @@ gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlL gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= -honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= +honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= @@ -731,5 +729,11 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= -tailscale.com v1.72.1 h1:hk82jek36ph2S3Tfsh57NVWKEm/pZ9nfUonvlowpfaA= -tailscale.com v1.72.1/go.mod h1:v7OHtg0KLAnhOVf81Z8WrjNefj238QbFhgkWJQoKxbs= +tailscale.com v1.75.0-pre.0.20240925091311-031f291c98fe h1:3+E/vlEsZa2FpWBz2Ly6/L4zh4utVO8z54Ms75HitrQ= +tailscale.com v1.75.0-pre.0.20240925091311-031f291c98fe/go.mod h1:G4R9objdXe2zAcLaLkDOcHfqN9XnspBifyBHGNwTzKg= +tailscale.com v1.75.0-pre.0.20240925102642-c17c476c0d59 h1:GSuB+bmPiVfBLRqVyLOFSU+9V00lXBz9HakAewevYZA= +tailscale.com v1.75.0-pre.0.20240925102642-c17c476c0d59/go.mod h1:G4R9objdXe2zAcLaLkDOcHfqN9XnspBifyBHGNwTzKg= +tailscale.com v1.75.0-pre.0.20240926030905-c90c9938c8a2 h1:ivZ1GEXMzCNI1VRp2TjUWmLuOtno7TqW26lZf7MlF4k= +tailscale.com v1.75.0-pre.0.20240926030905-c90c9938c8a2/go.mod h1:xKxYf3B3PuezFlRaMT+VhuVu8XTFUTLy+VCzLPMJVmg= +tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7 h1:nfRWV6ECxwNvvXKtbqSVstjlEi1BWktzv3FuxWpyyx0= +tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7/go.mod h1:xKxYf3B3PuezFlRaMT+VhuVu8XTFUTLy+VCzLPMJVmg= diff --git a/hscontrol/db/ip.go b/hscontrol/db/ip.go index d0e030d6..3525795a 100644 --- a/hscontrol/db/ip.go +++ b/hscontrol/db/ip.go @@ -14,6 +14,7 @@ import ( "github.com/rs/zerolog/log" "go4.org/netipx" "gorm.io/gorm" + "tailscale.com/net/tsaddr" ) // IPAllocator is a singleton responsible for allocating @@ -190,8 +191,9 @@ func (i *IPAllocator) next(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, return nil, ErrCouldNotAllocateIP } - // Check if the IP has already been allocated. - if set.Contains(ip) { + // Check if the IP has already been allocated + // or if it is a IP reserved by Tailscale. + if set.Contains(ip) || isTailscaleReservedIP(ip) { switch i.strategy { case types.IPAllocationStrategySequential: ip = ip.Next() @@ -248,6 +250,12 @@ func randomNext(pfx netip.Prefix) (netip.Addr, error) { return ip, nil } +func isTailscaleReservedIP(ip netip.Addr) bool { + return tsaddr.ChromeOSVMRange().Contains(ip) || + tsaddr.TailscaleServiceIP() == ip || + tsaddr.TailscaleServiceIPv6() == ip +} + // BackfillNodeIPs will take a database transaction, and // iterate through all of the current nodes in headscale // and ensure it has IP addresses according to the current diff --git a/hscontrol/db/ip_test.go b/hscontrol/db/ip_test.go index ce9c134c..b56d2d74 100644 --- a/hscontrol/db/ip_test.go +++ b/hscontrol/db/ip_test.go @@ -12,6 +12,9 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" + "github.com/stretchr/testify/assert" + "tailscale.com/net/tsaddr" + "tailscale.com/types/ptr" ) var mpp = func(pref string) *netip.Prefix { @@ -514,3 +517,26 @@ func TestBackfillIPAddresses(t *testing.T) { }) } } + +func TestIPAllocatorNextNoReservedIPs(t *testing.T) { + alloc, err := NewIPAllocator(db, ptr.To(tsaddr.CGNATRange()), ptr.To(tsaddr.TailscaleULARange()), types.IPAllocationStrategySequential) + if err != nil { + t.Fatalf("failed to set up ip alloc: %s", err) + } + + // Validate that we do not give out 100.100.100.100 + nextQuad100, err := alloc.next(na("100.100.100.99"), ptr.To(tsaddr.CGNATRange())) + assert.NoError(t, err) + assert.Equal(t, na("100.100.100.101"), *nextQuad100) + + // Validate that we do not give out fd7a:115c:a1e0::53 + nextQuad100v6, err := alloc.next(na("fd7a:115c:a1e0::52"), ptr.To(tsaddr.TailscaleULARange())) + assert.NoError(t, err) + assert.Equal(t, na("fd7a:115c:a1e0::54"), *nextQuad100v6) + + // Validate that we do not give out fd7a:115c:a1e0::53 + nextChrome, err := alloc.next(na("100.115.91.255"), ptr.To(tsaddr.CGNATRange())) + t.Logf("chrome: %s", nextChrome.String()) + assert.NoError(t, err) + assert.Equal(t, na("100.115.94.0"), *nextChrome) +} diff --git a/hscontrol/db/node.go b/hscontrol/db/node.go index c0f42de1..639354b3 100644 --- a/hscontrol/db/node.go +++ b/hscontrol/db/node.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/netip" + "slices" "sort" "sync" "time" @@ -215,7 +216,7 @@ func SetTags( var newTags types.StringList for _, tag := range tags { - if !util.StringOrPrefixListContains(newTags, tag) { + if !slices.Contains(newTags, tag) { newTags = append(newTags, tag) } } @@ -538,34 +539,24 @@ func IsRoutesEnabled(tx *gorm.DB, node *types.Node, routeStr string) bool { func (hsdb *HSDatabase) enableRoutes( node *types.Node, - routeStrs ...string, + newRoutes ...netip.Prefix, ) (*types.StateUpdate, error) { return Write(hsdb.DB, func(tx *gorm.DB) (*types.StateUpdate, error) { - return enableRoutes(tx, node, routeStrs...) + return enableRoutes(tx, node, newRoutes...) }) } // enableRoutes enables new routes based on a list of new routes. func enableRoutes(tx *gorm.DB, - node *types.Node, routeStrs ...string, + node *types.Node, newRoutes ...netip.Prefix, ) (*types.StateUpdate, error) { - newRoutes := make([]netip.Prefix, len(routeStrs)) - for index, routeStr := range routeStrs { - route, err := netip.ParsePrefix(routeStr) - if err != nil { - return nil, err - } - - newRoutes[index] = route - } - advertisedRoutes, err := GetAdvertisedRoutes(tx, node) if err != nil { return nil, err } for _, newRoute := range newRoutes { - if !util.StringOrPrefixListContains(advertisedRoutes, newRoute) { + if !slices.Contains(advertisedRoutes, newRoute) { return nil, fmt.Errorf( "route (%s) is not available on node %s: %w", node.Hostname, @@ -607,12 +598,6 @@ func enableRoutes(tx *gorm.DB, node.Routes = nRoutes - log.Trace(). - Caller(). - Str("node", node.Hostname). - Strs("routes", routeStrs). - Msg("enabling routes") - return &types.StateUpdate{ Type: types.StatePeerChanged, ChangeNodes: []types.NodeID{node.ID}, diff --git a/hscontrol/db/node_test.go b/hscontrol/db/node_test.go index bafb22ba..8451a906 100644 --- a/hscontrol/db/node_test.go +++ b/hscontrol/db/node_test.go @@ -6,7 +6,6 @@ import ( "math/big" "net/netip" "regexp" - "sort" "strconv" "sync" "testing" @@ -20,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "gopkg.in/check.v1" "gorm.io/gorm" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/ptr" @@ -528,16 +528,16 @@ func TestAutoApproveRoutes(t *testing.T) { } }`, routes: []netip.Prefix{ - netip.MustParsePrefix("0.0.0.0/0"), - netip.MustParsePrefix("::/0"), + tsaddr.AllIPv4(), + tsaddr.AllIPv6(), netip.MustParsePrefix("10.10.0.0/16"), netip.MustParsePrefix("10.11.0.0/24"), }, want: []netip.Prefix{ - netip.MustParsePrefix("::/0"), - netip.MustParsePrefix("10.11.0.0/24"), + tsaddr.AllIPv4(), netip.MustParsePrefix("10.10.0.0/16"), - netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("10.11.0.0/24"), + tsaddr.AllIPv6(), }, }, } @@ -594,9 +594,7 @@ func TestAutoApproveRoutes(t *testing.T) { assert.NoError(t, err) assert.Len(t, enabledRoutes, len(tt.want)) - sort.Slice(enabledRoutes, func(i, j int) bool { - return util.ComparePrefix(enabledRoutes[i], enabledRoutes[j]) > 0 - }) + tsaddr.SortPrefixes(enabledRoutes) if diff := cmp.Diff(tt.want, enabledRoutes, util.Comparers...); diff != "" { t.Errorf("unexpected enabled routes (-want +got):\n%s", diff) diff --git a/hscontrol/db/routes.go b/hscontrol/db/routes.go index fd837c29..0012d64e 100644 --- a/hscontrol/db/routes.go +++ b/hscontrol/db/routes.go @@ -11,6 +11,7 @@ import ( "github.com/puzpuzpuz/xsync/v3" "github.com/rs/zerolog/log" "gorm.io/gorm" + "tailscale.com/net/tsaddr" "tailscale.com/util/set" ) @@ -117,12 +118,12 @@ func EnableRoute(tx *gorm.DB, id uint64) (*types.StateUpdate, error) { return enableRoutes( tx, &route.Node, - types.ExitRouteV4.String(), - types.ExitRouteV6.String(), + tsaddr.AllIPv4(), + tsaddr.AllIPv6(), ) } - return enableRoutes(tx, &route.Node, netip.Prefix(route.Prefix).String()) + return enableRoutes(tx, &route.Node, netip.Prefix(route.Prefix)) } func DisableRoute(tx *gorm.DB, diff --git a/hscontrol/db/routes_test.go b/hscontrol/db/routes_test.go index 2324a21b..d71df312 100644 --- a/hscontrol/db/routes_test.go +++ b/hscontrol/db/routes_test.go @@ -27,6 +27,10 @@ var smap = func(m map[types.NodeID]bool) *xsync.MapOf[types.NodeID, bool] { return s } +var mp = func(p string) netip.Prefix { + return netip.MustParsePrefix(p) +} + func (s *Suite) TestGetRoutes(c *check.C) { user, err := db.CreateUser("test") c.Assert(err, check.IsNil) @@ -64,10 +68,10 @@ func (s *Suite) TestGetRoutes(c *check.C) { c.Assert(len(advertisedRoutes), check.Equals, 1) // TODO(kradalby): check state update - _, err = db.enableRoutes(&node, "192.168.0.0/24") + _, err = db.enableRoutes(&node, mp("192.168.0.0/24")) c.Assert(err, check.NotNil) - _, err = db.enableRoutes(&node, "10.0.0.0/24") + _, err = db.enableRoutes(&node, mp("10.0.0.0/24")) c.Assert(err, check.IsNil) } @@ -119,10 +123,10 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) { c.Assert(err, check.IsNil) c.Assert(len(noEnabledRoutes), check.Equals, 0) - _, err = db.enableRoutes(&node, "192.168.0.0/24") + _, err = db.enableRoutes(&node, mp("192.168.0.0/24")) c.Assert(err, check.NotNil) - _, err = db.enableRoutes(&node, "10.0.0.0/24") + _, err = db.enableRoutes(&node, mp("10.0.0.0/24")) c.Assert(err, check.IsNil) enabledRoutes, err := db.GetEnabledRoutes(&node) @@ -130,14 +134,14 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) { c.Assert(len(enabledRoutes), check.Equals, 1) // Adding it twice will just let it pass through - _, err = db.enableRoutes(&node, "10.0.0.0/24") + _, err = db.enableRoutes(&node, mp("10.0.0.0/24")) c.Assert(err, check.IsNil) enableRoutesAfterDoubleApply, err := db.GetEnabledRoutes(&node) c.Assert(err, check.IsNil) c.Assert(len(enableRoutesAfterDoubleApply), check.Equals, 1) - _, err = db.enableRoutes(&node, "150.0.10.0/25") + _, err = db.enableRoutes(&node, mp("150.0.10.0/25")) c.Assert(err, check.IsNil) enabledRoutesWithAdditionalRoute, err := db.GetEnabledRoutes(&node) @@ -183,10 +187,10 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) { c.Assert(err, check.IsNil) c.Assert(sendUpdate, check.Equals, false) - _, err = db.enableRoutes(&node1, route.String()) + _, err = db.enableRoutes(&node1, route) c.Assert(err, check.IsNil) - _, err = db.enableRoutes(&node1, route2.String()) + _, err = db.enableRoutes(&node1, route2) c.Assert(err, check.IsNil) hostInfo2 := tailcfg.Hostinfo{ @@ -206,7 +210,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) { c.Assert(err, check.IsNil) c.Assert(sendUpdate, check.Equals, false) - _, err = db.enableRoutes(&node2, route2.String()) + _, err = db.enableRoutes(&node2, route2) c.Assert(err, check.IsNil) enabledRoutes1, err := db.GetEnabledRoutes(&node1) @@ -267,10 +271,10 @@ func (s *Suite) TestDeleteRoutes(c *check.C) { c.Assert(err, check.IsNil) c.Assert(sendUpdate, check.Equals, false) - _, err = db.enableRoutes(&node1, prefix.String()) + _, err = db.enableRoutes(&node1, prefix) c.Assert(err, check.IsNil) - _, err = db.enableRoutes(&node1, prefix2.String()) + _, err = db.enableRoutes(&node1, prefix2) c.Assert(err, check.IsNil) routes, err := db.GetNodeRoutes(&node1) diff --git a/hscontrol/mapper/mapper_test.go b/hscontrol/mapper/mapper_test.go index 01f27261..89db69dc 100644 --- a/hscontrol/mapper/mapper_test.go +++ b/hscontrol/mapper/mapper_test.go @@ -12,6 +12,7 @@ import ( "github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/types" "gopkg.in/check.v1" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/dnstype" "tailscale.com/types/key" @@ -195,7 +196,7 @@ func Test_fullMapResponse(t *testing.T) { Hostinfo: &tailcfg.Hostinfo{}, Routes: []types.Route{ { - Prefix: types.IPPrefix(netip.MustParsePrefix("0.0.0.0/0")), + Prefix: types.IPPrefix(tsaddr.AllIPv4()), Advertised: true, Enabled: true, IsPrimary: false, @@ -234,7 +235,7 @@ func Test_fullMapResponse(t *testing.T) { Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")}, AllowedIPs: []netip.Prefix{ netip.MustParsePrefix("100.64.0.1/32"), - netip.MustParsePrefix("0.0.0.0/0"), + tsaddr.AllIPv4(), netip.MustParsePrefix("192.168.0.0/24"), }, DERP: "127.3.3.40:0", diff --git a/hscontrol/mapper/tail_test.go b/hscontrol/mapper/tail_test.go index c0d1c146..6e22cdcf 100644 --- a/hscontrol/mapper/tail_test.go +++ b/hscontrol/mapper/tail_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/types" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/key" ) @@ -108,7 +109,7 @@ func TestTailNode(t *testing.T) { Hostinfo: &tailcfg.Hostinfo{}, Routes: []types.Route{ { - Prefix: types.IPPrefix(netip.MustParsePrefix("0.0.0.0/0")), + Prefix: types.IPPrefix(tsaddr.AllIPv4()), Advertised: true, Enabled: true, IsPrimary: false, @@ -152,7 +153,7 @@ func TestTailNode(t *testing.T) { Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")}, AllowedIPs: []netip.Prefix{ netip.MustParsePrefix("100.64.0.1/32"), - netip.MustParsePrefix("0.0.0.0/0"), + tsaddr.AllIPv4(), netip.MustParsePrefix("192.168.0.0/24"), }, DERP: "127.3.3.40:0", diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index b166df03..f657d26f 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -7,6 +7,7 @@ import ( "io" "net/netip" "os" + "slices" "strconv" "strings" "time" @@ -16,6 +17,7 @@ import ( "github.com/rs/zerolog/log" "github.com/tailscale/hujson" "go4.org/netipx" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" ) @@ -45,7 +47,7 @@ func theInternet() *netipx.IPSet { var internetBuilder netipx.IPSetBuilder internetBuilder.AddPrefix(netip.MustParsePrefix("2000::/3")) - internetBuilder.AddPrefix(netip.MustParsePrefix("0.0.0.0/0")) + internetBuilder.AddPrefix(tsaddr.AllIPv4()) // Delete Private network addresses // https://datatracker.ietf.org/doc/html/rfc1918 @@ -55,8 +57,8 @@ func theInternet() *netipx.IPSet { internetBuilder.RemovePrefix(netip.MustParsePrefix("192.168.0.0/16")) // Delete Tailscale networks - internetBuilder.RemovePrefix(netip.MustParsePrefix("fd7a:115c:a1e0::/48")) - internetBuilder.RemovePrefix(netip.MustParsePrefix("100.64.0.0/10")) + internetBuilder.RemovePrefix(tsaddr.TailscaleULARange()) + internetBuilder.RemovePrefix(tsaddr.CGNATRange()) // Delete "cant find DHCP networks" internetBuilder.RemovePrefix(netip.MustParsePrefix("fe80::/10")) // link-loca @@ -603,7 +605,7 @@ func excludeCorrectlyTaggedNodes( for tag := range aclPolicy.TagOwners { owners, _ := expandOwnersFromTag(aclPolicy, user) ns := append(owners, user) - if util.StringOrPrefixListContains(ns, user) { + if slices.Contains(ns, user) { tags = append(tags, tag) } } @@ -616,7 +618,7 @@ func excludeCorrectlyTaggedNodes( } for _, t := range node.Hostinfo.RequestTags { - if util.StringOrPrefixListContains(tags, t) { + if slices.Contains(tags, t) { found = true break @@ -779,7 +781,7 @@ func (pol *ACLPolicy) expandIPsFromTag( // check for forced tags for _, node := range nodes { - if util.StringOrPrefixListContains(node.ForcedTags, alias) { + if slices.Contains(node.ForcedTags, alias) { node.AppendToIPSet(&build) } } @@ -811,7 +813,7 @@ func (pol *ACLPolicy) expandIPsFromTag( continue } - if util.StringOrPrefixListContains(node.Hostinfo.RequestTags, alias) { + if slices.Contains(node.Hostinfo.RequestTags, alias) { node.AppendToIPSet(&build) } } diff --git a/hscontrol/policy/acls_test.go b/hscontrol/policy/acls_test.go index 9f38c6db..20981224 100644 --- a/hscontrol/policy/acls_test.go +++ b/hscontrol/policy/acls_test.go @@ -3,6 +3,7 @@ package policy import ( "errors" "net/netip" + "slices" "testing" "github.com/google/go-cmp/cmp" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "go4.org/netipx" "gopkg.in/check.v1" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" ) @@ -341,7 +343,7 @@ func TestParsing(t *testing.T) { ], }, ], -} +} `, want: []tailcfg.FilterRule{ { @@ -1998,7 +2000,7 @@ func TestReduceFilterRules(t *testing.T) { IPv6: iap("fd7a:115c:a1e0::100"), User: types.User{Name: "user100"}, Hostinfo: &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6}, + RoutableIPs: tsaddr.ExitRoutes(), }, }, }, @@ -2036,7 +2038,7 @@ func TestReduceFilterRules(t *testing.T) { IPv6: iap("fd7a:115c:a1e0::100"), User: types.User{Name: "user100"}, Hostinfo: &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6}, + RoutableIPs: tsaddr.ExitRoutes(), }, }, peers: types.Nodes{ @@ -2132,7 +2134,7 @@ func TestReduceFilterRules(t *testing.T) { IPv6: iap("fd7a:115c:a1e0::100"), User: types.User{Name: "user100"}, Hostinfo: &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6}, + RoutableIPs: tsaddr.ExitRoutes(), }, }, peers: types.Nodes{ @@ -2548,7 +2550,7 @@ func Test_getTags(t *testing.T) { test.args.node, ) for _, valid := range gotValid { - if !util.StringOrPrefixListContains(test.wantValid, valid) { + if !slices.Contains(test.wantValid, valid) { t.Errorf( "valids: getTags() = %v, want %v", gotValid, @@ -2559,7 +2561,7 @@ func Test_getTags(t *testing.T) { } } for _, invalid := range gotInvalid { - if !util.StringOrPrefixListContains(test.wantInvalid, invalid) { + if !slices.Contains(test.wantInvalid, invalid) { t.Errorf( "invalids: getTags() = %v, want %v", gotInvalid, diff --git a/hscontrol/poll.go b/hscontrol/poll.go index 252f338b..033639ae 100644 --- a/hscontrol/poll.go +++ b/hscontrol/poll.go @@ -6,18 +6,17 @@ import ( "math/rand/v2" "net/http" "slices" - "sort" "strings" "time" "github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/mapper" "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" "github.com/rs/zerolog/log" "github.com/sasha-s/go-deadlock" xslices "golang.org/x/exp/slices" "gorm.io/gorm" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" ) @@ -666,12 +665,8 @@ func hostInfoChanged(old, new *tailcfg.Hostinfo) (bool, bool) { oldRoutes := old.RoutableIPs newRoutes := new.RoutableIPs - sort.Slice(oldRoutes, func(i, j int) bool { - return util.ComparePrefix(oldRoutes[i], oldRoutes[j]) > 0 - }) - sort.Slice(newRoutes, func(i, j int) bool { - return util.ComparePrefix(newRoutes[i], newRoutes[j]) > 0 - }) + tsaddr.SortPrefixes(oldRoutes) + tsaddr.SortPrefixes(newRoutes) if !xslices.Equal(oldRoutes, newRoutes) { return true, true diff --git a/hscontrol/types/routes.go b/hscontrol/types/routes.go index 697cbc36..04118fa6 100644 --- a/hscontrol/types/routes.go +++ b/hscontrol/types/routes.go @@ -7,11 +7,7 @@ import ( v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "google.golang.org/protobuf/types/known/timestamppb" "gorm.io/gorm" -) - -var ( - ExitRouteV4 = netip.MustParsePrefix("0.0.0.0/0") - ExitRouteV6 = netip.MustParsePrefix("::/0") + "tailscale.com/net/tsaddr" ) type Route struct { @@ -35,7 +31,7 @@ func (r *Route) String() string { } func (r *Route) IsExitRoute() bool { - return netip.Prefix(r.Prefix) == ExitRouteV4 || netip.Prefix(r.Prefix) == ExitRouteV6 + return tsaddr.IsExitRoute(netip.Prefix(r.Prefix)) } func (r *Route) IsAnnouncable() bool { diff --git a/hscontrol/util/addr.go b/hscontrol/util/addr.go index 5c02c933..b755a8e7 100644 --- a/hscontrol/util/addr.go +++ b/hscontrol/util/addr.go @@ -3,7 +3,6 @@ package util import ( "fmt" "net/netip" - "reflect" "strings" "go4.org/netipx" @@ -104,7 +103,7 @@ func StringToIPPrefix(prefixes []string) ([]netip.Prefix, error) { for index, prefixStr := range prefixes { prefix, err := netip.ParsePrefix(prefixStr) if err != nil { - return []netip.Prefix{}, err + return nil, err } result[index] = prefix @@ -112,13 +111,3 @@ func StringToIPPrefix(prefixes []string) ([]netip.Prefix, error) { return result, nil } - -func StringOrPrefixListContains[T string | netip.Prefix](ts []T, t T) bool { - for _, v := range ts { - if reflect.DeepEqual(v, t) { - return true - } - } - - return false -} diff --git a/hscontrol/util/key.go b/hscontrol/util/key.go index 6501daca..ae107053 100644 --- a/hscontrol/util/key.go +++ b/hscontrol/util/key.go @@ -1,33 +1,10 @@ package util import ( - "encoding/json" "errors" - "regexp" - - "tailscale.com/types/key" ) var ( - NodePublicKeyRegex = regexp.MustCompile("nodekey:[a-fA-F0-9]+") ErrCannotDecryptResponse = errors.New("cannot decrypt response") ZstdCompression = "zstd" ) - -func DecodeAndUnmarshalNaCl( - msg []byte, - output interface{}, - pubKey *key.MachinePublic, - privKey *key.MachinePrivate, -) error { - decrypted, ok := privKey.OpenFrom(*pubKey, msg) - if !ok { - return ErrCannotDecryptResponse - } - - if err := json.Unmarshal(decrypted, output); err != nil { - return err - } - - return nil -} diff --git a/hscontrol/util/net.go b/hscontrol/util/net.go index 59a8d635..b704c936 100644 --- a/hscontrol/util/net.go +++ b/hscontrol/util/net.go @@ -1,10 +1,8 @@ package util import ( - "cmp" "context" "net" - "net/netip" ) func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) { @@ -12,19 +10,3 @@ func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) { return d.DialContext(ctx, "unix", addr) } - -// TODO(kradalby): Remove after go 1.24, will be in stdlib. -// Compare returns an integer comparing two prefixes. -// The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2. -// Prefixes sort first by validity (invalid before valid), then -// address family (IPv4 before IPv6), then prefix length, then -// address. -func ComparePrefix(p, p2 netip.Prefix) int { - if c := cmp.Compare(p.Addr().BitLen(), p2.Addr().BitLen()); c != 0 { - return c - } - if c := cmp.Compare(p.Bits(), p2.Bits()); c != 0 { - return c - } - return p.Addr().Compare(p2.Addr()) -}