diff --git a/CHANGELOG.md b/CHANGELOG.md index c0186961..54ad9a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/ - `/var/lib/headscale` and `/var/run/headscale` is no longer created automatically, see [container docs](./docs/running-headscale-container.md) - Prefixes are now defined per v4 and v6 range. [#1756](https://github.com/juanfont/headscale/pull/1756) - `ip_prefixes` option is now `prefixes.v4` and `prefixes.v6` + - `prefixes.allocation` can be set to assign IPs at `sequential` or `random`. [#1869](https://github.com/juanfont/headscale/pull/1869) ### Changes @@ -53,6 +54,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/ - Turn off gRPC logging [#1640](https://github.com/juanfont/headscale/pull/1640) fixes [#1259](https://github.com/juanfont/headscale/issues/1259) - Added the possibility to manually create a DERP-map entry which can be customized, instead of automatically creating it. [#1565](https://github.com/juanfont/headscale/pull/1565) - Add support for deleting api keys [#1702](https://github.com/juanfont/headscale/pull/1702) +- Add command to backfill IP addresses for nodes missing IPs from configured prefixes. [#1869](https://github.com/juanfont/headscale/pull/1869) ## 0.22.3 (2023-05-12) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index ac996245..58890cb0 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -97,6 +97,8 @@ func init() { tagCmd.Flags(). StringSliceP("tags", "t", []string{}, "List of tags to add to the node") nodeCmd.AddCommand(tagCmd) + + nodeCmd.AddCommand(backfillNodeIPsCmd) } var nodeCmd = &cobra.Command{ @@ -477,6 +479,57 @@ var moveNodeCmd = &cobra.Command{ }, } +var backfillNodeIPsCmd = &cobra.Command{ + Use: "backfillips", + Short: "Backfill IPs missing from nodes", + Long: ` +Backfill IPs can be used to add/remove IPs from nodes +based on the current configuration of Headscale. + +If there are nodes that does not have IPv4 or IPv6 +even if prefixes for both are configured in the config, +this command can be used to assign IPs of the sort to +all nodes that are missing. + +If you remove IPv4 or IPv6 prefixes from the config, +it can be run to remove the IPs that should no longer +be assigned to nodes.`, + Run: func(cmd *cobra.Command, args []string) { + var err error + output, _ := cmd.Flags().GetString("output") + + confirm := false + prompt := &survey.Confirm{ + Message: "Are you sure that you want to assign/remove IPs to/from nodes?", + } + err = survey.AskOne(prompt, &confirm) + if err != nil { + return + } + if confirm { + ctx, client, conn, cancel := getHeadscaleCLIClient() + defer cancel() + defer conn.Close() + + changes, err := client.BackfillNodeIPs(ctx, &v1.BackfillNodeIPsRequest{Confirmed: confirm}) + if err != nil { + ErrorOutput( + err, + fmt.Sprintf( + "Error backfilling IPs: %s", + status.Convert(err).Message(), + ), + output, + ) + + return + } + + SuccessOutput(changes, "Node IPs backfilled successfully", output) + } + }, +} + func nodesToPtables( currentUser string, showTags bool, diff --git a/config-example.yaml b/config-example.yaml index ba81ba5d..ac0a5eb8 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -61,6 +61,11 @@ prefixes: v6: fd7a:115c:a1e0::/48 v4: 100.64.0.0/10 + # Strategy used for allocation of IPs to nodes, available options: + # - sequential (default): assigns the next free IP from the previous given IP. + # - random: assigns the next free IP from a pseudo-random IP generator (crypto/rand). + allocation: sequential + # DERP is a relay system that Tailscale uses when a direct # connection cannot be established. # https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp diff --git a/gen/go/headscale/v1/apikey.pb.go b/gen/go/headscale/v1/apikey.pb.go index d1a5f555..c4377e48 100644 --- a/gen/go/headscale/v1/apikey.pb.go +++ b/gen/go/headscale/v1/apikey.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/apikey.proto diff --git a/gen/go/headscale/v1/device.pb.go b/gen/go/headscale/v1/device.pb.go index 40e2e24f..7a382dd6 100644 --- a/gen/go/headscale/v1/device.pb.go +++ b/gen/go/headscale/v1/device.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/device.proto diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go index b1af2fa5..9de6b060 100644 --- a/gen/go/headscale/v1/headscale.pb.go +++ b/gen/go/headscale/v1/headscale.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/headscale.proto @@ -36,7 +36,7 @@ var file_headscale_v1_headscale_proto_rawDesc = []byte{ 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x32, 0xfd, 0x17, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, + 0x6f, 0x32, 0x80, 0x19, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, @@ -161,77 +161,85 @@ var file_headscale_v1_headscale_proto_rawDesc = []byte{ 0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75, - 0x73, 0x65, 0x72, 0x12, 0x64, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, - 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x7c, 0x0a, 0x0b, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, - 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61, - 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d, - 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, - 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, + 0x73, 0x65, 0x72, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, + 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, + 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, + 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, + 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x66, + 0x69, 0x6c, 0x6c, 0x69, 0x70, 0x73, 0x12, 0x64, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x7c, 0x0a, 0x0b, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, + 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, + 0x69, 0x64, 0x7d, 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x44, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, - 0x64, 0x7d, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x7f, 0x0a, 0x0d, 0x47, 0x65, - 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x65, - 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, - 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, - 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x75, 0x0a, 0x0b, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, - 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, - 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, - 0x64, 0x7d, 0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, - 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, + 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x7f, 0x0a, + 0x0d, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x22, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, + 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, + 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x75, + 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, + 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, - 0x69, 0x6b, 0x65, 0x79, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, - 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, + 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, + 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, - 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, - 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x6a, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x20, 0x2e, 0x68, - 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, - 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x76, 0x0a, 0x0c, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, - 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, + 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, + 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x12, 0x6a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12, + 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x76, 0x0a, 0x0c, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x7b, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, - 0x7d, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, - 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x7b, 0x70, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x7d, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var file_headscale_v1_headscale_proto_goTypes = []interface{}{ @@ -252,41 +260,43 @@ var file_headscale_v1_headscale_proto_goTypes = []interface{}{ (*RenameNodeRequest)(nil), // 14: headscale.v1.RenameNodeRequest (*ListNodesRequest)(nil), // 15: headscale.v1.ListNodesRequest (*MoveNodeRequest)(nil), // 16: headscale.v1.MoveNodeRequest - (*GetRoutesRequest)(nil), // 17: headscale.v1.GetRoutesRequest - (*EnableRouteRequest)(nil), // 18: headscale.v1.EnableRouteRequest - (*DisableRouteRequest)(nil), // 19: headscale.v1.DisableRouteRequest - (*GetNodeRoutesRequest)(nil), // 20: headscale.v1.GetNodeRoutesRequest - (*DeleteRouteRequest)(nil), // 21: headscale.v1.DeleteRouteRequest - (*CreateApiKeyRequest)(nil), // 22: headscale.v1.CreateApiKeyRequest - (*ExpireApiKeyRequest)(nil), // 23: headscale.v1.ExpireApiKeyRequest - (*ListApiKeysRequest)(nil), // 24: headscale.v1.ListApiKeysRequest - (*DeleteApiKeyRequest)(nil), // 25: headscale.v1.DeleteApiKeyRequest - (*GetUserResponse)(nil), // 26: headscale.v1.GetUserResponse - (*CreateUserResponse)(nil), // 27: headscale.v1.CreateUserResponse - (*RenameUserResponse)(nil), // 28: headscale.v1.RenameUserResponse - (*DeleteUserResponse)(nil), // 29: headscale.v1.DeleteUserResponse - (*ListUsersResponse)(nil), // 30: headscale.v1.ListUsersResponse - (*CreatePreAuthKeyResponse)(nil), // 31: headscale.v1.CreatePreAuthKeyResponse - (*ExpirePreAuthKeyResponse)(nil), // 32: headscale.v1.ExpirePreAuthKeyResponse - (*ListPreAuthKeysResponse)(nil), // 33: headscale.v1.ListPreAuthKeysResponse - (*DebugCreateNodeResponse)(nil), // 34: headscale.v1.DebugCreateNodeResponse - (*GetNodeResponse)(nil), // 35: headscale.v1.GetNodeResponse - (*SetTagsResponse)(nil), // 36: headscale.v1.SetTagsResponse - (*RegisterNodeResponse)(nil), // 37: headscale.v1.RegisterNodeResponse - (*DeleteNodeResponse)(nil), // 38: headscale.v1.DeleteNodeResponse - (*ExpireNodeResponse)(nil), // 39: headscale.v1.ExpireNodeResponse - (*RenameNodeResponse)(nil), // 40: headscale.v1.RenameNodeResponse - (*ListNodesResponse)(nil), // 41: headscale.v1.ListNodesResponse - (*MoveNodeResponse)(nil), // 42: headscale.v1.MoveNodeResponse - (*GetRoutesResponse)(nil), // 43: headscale.v1.GetRoutesResponse - (*EnableRouteResponse)(nil), // 44: headscale.v1.EnableRouteResponse - (*DisableRouteResponse)(nil), // 45: headscale.v1.DisableRouteResponse - (*GetNodeRoutesResponse)(nil), // 46: headscale.v1.GetNodeRoutesResponse - (*DeleteRouteResponse)(nil), // 47: headscale.v1.DeleteRouteResponse - (*CreateApiKeyResponse)(nil), // 48: headscale.v1.CreateApiKeyResponse - (*ExpireApiKeyResponse)(nil), // 49: headscale.v1.ExpireApiKeyResponse - (*ListApiKeysResponse)(nil), // 50: headscale.v1.ListApiKeysResponse - (*DeleteApiKeyResponse)(nil), // 51: headscale.v1.DeleteApiKeyResponse + (*BackfillNodeIPsRequest)(nil), // 17: headscale.v1.BackfillNodeIPsRequest + (*GetRoutesRequest)(nil), // 18: headscale.v1.GetRoutesRequest + (*EnableRouteRequest)(nil), // 19: headscale.v1.EnableRouteRequest + (*DisableRouteRequest)(nil), // 20: headscale.v1.DisableRouteRequest + (*GetNodeRoutesRequest)(nil), // 21: headscale.v1.GetNodeRoutesRequest + (*DeleteRouteRequest)(nil), // 22: headscale.v1.DeleteRouteRequest + (*CreateApiKeyRequest)(nil), // 23: headscale.v1.CreateApiKeyRequest + (*ExpireApiKeyRequest)(nil), // 24: headscale.v1.ExpireApiKeyRequest + (*ListApiKeysRequest)(nil), // 25: headscale.v1.ListApiKeysRequest + (*DeleteApiKeyRequest)(nil), // 26: headscale.v1.DeleteApiKeyRequest + (*GetUserResponse)(nil), // 27: headscale.v1.GetUserResponse + (*CreateUserResponse)(nil), // 28: headscale.v1.CreateUserResponse + (*RenameUserResponse)(nil), // 29: headscale.v1.RenameUserResponse + (*DeleteUserResponse)(nil), // 30: headscale.v1.DeleteUserResponse + (*ListUsersResponse)(nil), // 31: headscale.v1.ListUsersResponse + (*CreatePreAuthKeyResponse)(nil), // 32: headscale.v1.CreatePreAuthKeyResponse + (*ExpirePreAuthKeyResponse)(nil), // 33: headscale.v1.ExpirePreAuthKeyResponse + (*ListPreAuthKeysResponse)(nil), // 34: headscale.v1.ListPreAuthKeysResponse + (*DebugCreateNodeResponse)(nil), // 35: headscale.v1.DebugCreateNodeResponse + (*GetNodeResponse)(nil), // 36: headscale.v1.GetNodeResponse + (*SetTagsResponse)(nil), // 37: headscale.v1.SetTagsResponse + (*RegisterNodeResponse)(nil), // 38: headscale.v1.RegisterNodeResponse + (*DeleteNodeResponse)(nil), // 39: headscale.v1.DeleteNodeResponse + (*ExpireNodeResponse)(nil), // 40: headscale.v1.ExpireNodeResponse + (*RenameNodeResponse)(nil), // 41: headscale.v1.RenameNodeResponse + (*ListNodesResponse)(nil), // 42: headscale.v1.ListNodesResponse + (*MoveNodeResponse)(nil), // 43: headscale.v1.MoveNodeResponse + (*BackfillNodeIPsResponse)(nil), // 44: headscale.v1.BackfillNodeIPsResponse + (*GetRoutesResponse)(nil), // 45: headscale.v1.GetRoutesResponse + (*EnableRouteResponse)(nil), // 46: headscale.v1.EnableRouteResponse + (*DisableRouteResponse)(nil), // 47: headscale.v1.DisableRouteResponse + (*GetNodeRoutesResponse)(nil), // 48: headscale.v1.GetNodeRoutesResponse + (*DeleteRouteResponse)(nil), // 49: headscale.v1.DeleteRouteResponse + (*CreateApiKeyResponse)(nil), // 50: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyResponse)(nil), // 51: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysResponse)(nil), // 52: headscale.v1.ListApiKeysResponse + (*DeleteApiKeyResponse)(nil), // 53: headscale.v1.DeleteApiKeyResponse } var file_headscale_v1_headscale_proto_depIdxs = []int32{ 0, // 0: headscale.v1.HeadscaleService.GetUser:input_type -> headscale.v1.GetUserRequest @@ -306,43 +316,45 @@ var file_headscale_v1_headscale_proto_depIdxs = []int32{ 14, // 14: headscale.v1.HeadscaleService.RenameNode:input_type -> headscale.v1.RenameNodeRequest 15, // 15: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest 16, // 16: headscale.v1.HeadscaleService.MoveNode:input_type -> headscale.v1.MoveNodeRequest - 17, // 17: headscale.v1.HeadscaleService.GetRoutes:input_type -> headscale.v1.GetRoutesRequest - 18, // 18: headscale.v1.HeadscaleService.EnableRoute:input_type -> headscale.v1.EnableRouteRequest - 19, // 19: headscale.v1.HeadscaleService.DisableRoute:input_type -> headscale.v1.DisableRouteRequest - 20, // 20: headscale.v1.HeadscaleService.GetNodeRoutes:input_type -> headscale.v1.GetNodeRoutesRequest - 21, // 21: headscale.v1.HeadscaleService.DeleteRoute:input_type -> headscale.v1.DeleteRouteRequest - 22, // 22: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest - 23, // 23: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest - 24, // 24: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest - 25, // 25: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest - 26, // 26: headscale.v1.HeadscaleService.GetUser:output_type -> headscale.v1.GetUserResponse - 27, // 27: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse - 28, // 28: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse - 29, // 29: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse - 30, // 30: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse - 31, // 31: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse - 32, // 32: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse - 33, // 33: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse - 34, // 34: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse - 35, // 35: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse - 36, // 36: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse - 37, // 37: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse - 38, // 38: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse - 39, // 39: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse - 40, // 40: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse - 41, // 41: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse - 42, // 42: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse - 43, // 43: headscale.v1.HeadscaleService.GetRoutes:output_type -> headscale.v1.GetRoutesResponse - 44, // 44: headscale.v1.HeadscaleService.EnableRoute:output_type -> headscale.v1.EnableRouteResponse - 45, // 45: headscale.v1.HeadscaleService.DisableRoute:output_type -> headscale.v1.DisableRouteResponse - 46, // 46: headscale.v1.HeadscaleService.GetNodeRoutes:output_type -> headscale.v1.GetNodeRoutesResponse - 47, // 47: headscale.v1.HeadscaleService.DeleteRoute:output_type -> headscale.v1.DeleteRouteResponse - 48, // 48: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse - 49, // 49: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse - 50, // 50: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse - 51, // 51: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse - 26, // [26:52] is the sub-list for method output_type - 0, // [0:26] is the sub-list for method input_type + 17, // 17: headscale.v1.HeadscaleService.BackfillNodeIPs:input_type -> headscale.v1.BackfillNodeIPsRequest + 18, // 18: headscale.v1.HeadscaleService.GetRoutes:input_type -> headscale.v1.GetRoutesRequest + 19, // 19: headscale.v1.HeadscaleService.EnableRoute:input_type -> headscale.v1.EnableRouteRequest + 20, // 20: headscale.v1.HeadscaleService.DisableRoute:input_type -> headscale.v1.DisableRouteRequest + 21, // 21: headscale.v1.HeadscaleService.GetNodeRoutes:input_type -> headscale.v1.GetNodeRoutesRequest + 22, // 22: headscale.v1.HeadscaleService.DeleteRoute:input_type -> headscale.v1.DeleteRouteRequest + 23, // 23: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest + 24, // 24: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest + 25, // 25: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest + 26, // 26: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest + 27, // 27: headscale.v1.HeadscaleService.GetUser:output_type -> headscale.v1.GetUserResponse + 28, // 28: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse + 29, // 29: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse + 30, // 30: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse + 31, // 31: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse + 32, // 32: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse + 33, // 33: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse + 34, // 34: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse + 35, // 35: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse + 36, // 36: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse + 37, // 37: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse + 38, // 38: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse + 39, // 39: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse + 40, // 40: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse + 41, // 41: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse + 42, // 42: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse + 43, // 43: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse + 44, // 44: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse + 45, // 45: headscale.v1.HeadscaleService.GetRoutes:output_type -> headscale.v1.GetRoutesResponse + 46, // 46: headscale.v1.HeadscaleService.EnableRoute:output_type -> headscale.v1.EnableRouteResponse + 47, // 47: headscale.v1.HeadscaleService.DisableRoute:output_type -> headscale.v1.DisableRouteResponse + 48, // 48: headscale.v1.HeadscaleService.GetNodeRoutes:output_type -> headscale.v1.GetNodeRoutesResponse + 49, // 49: headscale.v1.HeadscaleService.DeleteRoute:output_type -> headscale.v1.DeleteRouteResponse + 50, // 50: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse + 51, // 51: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse + 52, // 52: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse + 53, // 53: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse + 27, // [27:54] is the sub-list for method output_type + 0, // [0:27] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/gen/go/headscale/v1/headscale.pb.gw.go b/gen/go/headscale/v1/headscale.pb.gw.go index b46f383b..adc7beeb 100644 --- a/gen/go/headscale/v1/headscale.pb.gw.go +++ b/gen/go/headscale/v1/headscale.pb.gw.go @@ -795,6 +795,42 @@ func local_request_HeadscaleService_MoveNode_0(ctx context.Context, marshaler ru } +var ( + filter_HeadscaleService_BackfillNodeIPs_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_HeadscaleService_BackfillNodeIPs_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq BackfillNodeIPsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_BackfillNodeIPs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.BackfillNodeIPs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_HeadscaleService_BackfillNodeIPs_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq BackfillNodeIPsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_BackfillNodeIPs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.BackfillNodeIPs(ctx, &protoReq) + return msg, metadata, err + +} + func request_HeadscaleService_GetRoutes_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetRoutesRequest var metadata runtime.ServerMetadata @@ -1574,6 +1610,31 @@ func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.Ser }) + mux.Handle("POST", pattern_HeadscaleService_BackfillNodeIPs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/BackfillNodeIPs", runtime.WithHTTPPathPattern("/api/v1/node/backfillips")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_BackfillNodeIPs_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_HeadscaleService_BackfillNodeIPs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_HeadscaleService_GetRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2214,6 +2275,28 @@ func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.Ser }) + mux.Handle("POST", pattern_HeadscaleService_BackfillNodeIPs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/BackfillNodeIPs", runtime.WithHTTPPathPattern("/api/v1/node/backfillips")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_BackfillNodeIPs_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_HeadscaleService_BackfillNodeIPs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_HeadscaleService_GetRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2450,6 +2533,8 @@ var ( pattern_HeadscaleService_MoveNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "node", "node_id", "user"}, "")) + pattern_HeadscaleService_BackfillNodeIPs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "node", "backfillips"}, "")) + pattern_HeadscaleService_GetRoutes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "routes"}, "")) pattern_HeadscaleService_EnableRoute_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "routes", "route_id", "enable"}, "")) @@ -2504,6 +2589,8 @@ var ( forward_HeadscaleService_MoveNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_BackfillNodeIPs_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_GetRoutes_0 = runtime.ForwardResponseMessage forward_HeadscaleService_EnableRoute_0 = runtime.ForwardResponseMessage diff --git a/gen/go/headscale/v1/headscale_grpc.pb.go b/gen/go/headscale/v1/headscale_grpc.pb.go index 0d731adc..6557f880 100644 --- a/gen/go/headscale/v1/headscale_grpc.pb.go +++ b/gen/go/headscale/v1/headscale_grpc.pb.go @@ -36,6 +36,7 @@ const ( HeadscaleService_RenameNode_FullMethodName = "/headscale.v1.HeadscaleService/RenameNode" HeadscaleService_ListNodes_FullMethodName = "/headscale.v1.HeadscaleService/ListNodes" HeadscaleService_MoveNode_FullMethodName = "/headscale.v1.HeadscaleService/MoveNode" + HeadscaleService_BackfillNodeIPs_FullMethodName = "/headscale.v1.HeadscaleService/BackfillNodeIPs" HeadscaleService_GetRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetRoutes" HeadscaleService_EnableRoute_FullMethodName = "/headscale.v1.HeadscaleService/EnableRoute" HeadscaleService_DisableRoute_FullMethodName = "/headscale.v1.HeadscaleService/DisableRoute" @@ -71,6 +72,7 @@ type HeadscaleServiceClient interface { RenameNode(ctx context.Context, in *RenameNodeRequest, opts ...grpc.CallOption) (*RenameNodeResponse, error) ListNodes(ctx context.Context, in *ListNodesRequest, opts ...grpc.CallOption) (*ListNodesResponse, error) MoveNode(ctx context.Context, in *MoveNodeRequest, opts ...grpc.CallOption) (*MoveNodeResponse, error) + BackfillNodeIPs(ctx context.Context, in *BackfillNodeIPsRequest, opts ...grpc.CallOption) (*BackfillNodeIPsResponse, error) // --- Route start --- GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error) EnableRoute(ctx context.Context, in *EnableRouteRequest, opts ...grpc.CallOption) (*EnableRouteResponse, error) @@ -245,6 +247,15 @@ func (c *headscaleServiceClient) MoveNode(ctx context.Context, in *MoveNodeReque return out, nil } +func (c *headscaleServiceClient) BackfillNodeIPs(ctx context.Context, in *BackfillNodeIPsRequest, opts ...grpc.CallOption) (*BackfillNodeIPsResponse, error) { + out := new(BackfillNodeIPsResponse) + err := c.cc.Invoke(ctx, HeadscaleService_BackfillNodeIPs_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *headscaleServiceClient) GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error) { out := new(GetRoutesResponse) err := c.cc.Invoke(ctx, HeadscaleService_GetRoutes_FullMethodName, in, out, opts...) @@ -350,6 +361,7 @@ type HeadscaleServiceServer interface { RenameNode(context.Context, *RenameNodeRequest) (*RenameNodeResponse, error) ListNodes(context.Context, *ListNodesRequest) (*ListNodesResponse, error) MoveNode(context.Context, *MoveNodeRequest) (*MoveNodeResponse, error) + BackfillNodeIPs(context.Context, *BackfillNodeIPsRequest) (*BackfillNodeIPsResponse, error) // --- Route start --- GetRoutes(context.Context, *GetRoutesRequest) (*GetRoutesResponse, error) EnableRoute(context.Context, *EnableRouteRequest) (*EnableRouteResponse, error) @@ -419,6 +431,9 @@ func (UnimplementedHeadscaleServiceServer) ListNodes(context.Context, *ListNodes func (UnimplementedHeadscaleServiceServer) MoveNode(context.Context, *MoveNodeRequest) (*MoveNodeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method MoveNode not implemented") } +func (UnimplementedHeadscaleServiceServer) BackfillNodeIPs(context.Context, *BackfillNodeIPsRequest) (*BackfillNodeIPsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BackfillNodeIPs not implemented") +} func (UnimplementedHeadscaleServiceServer) GetRoutes(context.Context, *GetRoutesRequest) (*GetRoutesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRoutes not implemented") } @@ -765,6 +780,24 @@ func _HeadscaleService_MoveNode_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _HeadscaleService_BackfillNodeIPs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackfillNodeIPsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).BackfillNodeIPs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_BackfillNodeIPs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).BackfillNodeIPs(ctx, req.(*BackfillNodeIPsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _HeadscaleService_GetRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRoutesRequest) if err := dec(in); err != nil { @@ -1002,6 +1035,10 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{ MethodName: "MoveNode", Handler: _HeadscaleService_MoveNode_Handler, }, + { + MethodName: "BackfillNodeIPs", + Handler: _HeadscaleService_BackfillNodeIPs_Handler, + }, { MethodName: "GetRoutes", Handler: _HeadscaleService_GetRoutes_Handler, diff --git a/gen/go/headscale/v1/node.pb.go b/gen/go/headscale/v1/node.pb.go index ee031566..93d2c6b0 100644 --- a/gen/go/headscale/v1/node.pb.go +++ b/gen/go/headscale/v1/node.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/node.proto @@ -1141,6 +1141,100 @@ func (x *DebugCreateNodeResponse) GetNode() *Node { return nil } +type BackfillNodeIPsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Confirmed bool `protobuf:"varint,1,opt,name=confirmed,proto3" json:"confirmed,omitempty"` +} + +func (x *BackfillNodeIPsRequest) Reset() { + *x = BackfillNodeIPsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_node_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackfillNodeIPsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackfillNodeIPsRequest) ProtoMessage() {} + +func (x *BackfillNodeIPsRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackfillNodeIPsRequest.ProtoReflect.Descriptor instead. +func (*BackfillNodeIPsRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{19} +} + +func (x *BackfillNodeIPsRequest) GetConfirmed() bool { + if x != nil { + return x.Confirmed + } + return false +} + +type BackfillNodeIPsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Changes []string `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"` +} + +func (x *BackfillNodeIPsResponse) Reset() { + *x = BackfillNodeIPsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_node_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackfillNodeIPsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackfillNodeIPsResponse) ProtoMessage() {} + +func (x *BackfillNodeIPsResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackfillNodeIPsResponse.ProtoReflect.Descriptor instead. +func (*BackfillNodeIPsResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{20} +} + +func (x *BackfillNodeIPsResponse) GetChanges() []string { + if x != nil { + return x.Changes + } + return nil +} + var File_headscale_v1_node_proto protoreflect.FileDescriptor var file_headscale_v1_node_proto_rawDesc = []byte{ @@ -1260,18 +1354,25 @@ var file_headscale_v1_node_proto_rawDesc = []byte{ 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, - 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, - 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, - 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, - 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, - 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, - 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x03, 0x42, 0x29, 0x5a, - 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, - 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x36, 0x0a, 0x16, 0x42, 0x61, 0x63, 0x6b, + 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, + 0x22, 0x33, 0x0a, 0x17, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, + 0x49, 0x50, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, + 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, + 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, + 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, + 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, + 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, + 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, + 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, + 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1287,7 +1388,7 @@ func file_headscale_v1_node_proto_rawDescGZIP() []byte { } var file_headscale_v1_node_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_headscale_v1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_headscale_v1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_headscale_v1_node_proto_goTypes = []interface{}{ (RegisterMethod)(0), // 0: headscale.v1.RegisterMethod (*Node)(nil), // 1: headscale.v1.Node @@ -1309,16 +1410,18 @@ var file_headscale_v1_node_proto_goTypes = []interface{}{ (*MoveNodeResponse)(nil), // 17: headscale.v1.MoveNodeResponse (*DebugCreateNodeRequest)(nil), // 18: headscale.v1.DebugCreateNodeRequest (*DebugCreateNodeResponse)(nil), // 19: headscale.v1.DebugCreateNodeResponse - (*User)(nil), // 20: headscale.v1.User - (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp - (*PreAuthKey)(nil), // 22: headscale.v1.PreAuthKey + (*BackfillNodeIPsRequest)(nil), // 20: headscale.v1.BackfillNodeIPsRequest + (*BackfillNodeIPsResponse)(nil), // 21: headscale.v1.BackfillNodeIPsResponse + (*User)(nil), // 22: headscale.v1.User + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*PreAuthKey)(nil), // 24: headscale.v1.PreAuthKey } var file_headscale_v1_node_proto_depIdxs = []int32{ - 20, // 0: headscale.v1.Node.user:type_name -> headscale.v1.User - 21, // 1: headscale.v1.Node.last_seen:type_name -> google.protobuf.Timestamp - 21, // 2: headscale.v1.Node.expiry:type_name -> google.protobuf.Timestamp - 22, // 3: headscale.v1.Node.pre_auth_key:type_name -> headscale.v1.PreAuthKey - 21, // 4: headscale.v1.Node.created_at:type_name -> google.protobuf.Timestamp + 22, // 0: headscale.v1.Node.user:type_name -> headscale.v1.User + 23, // 1: headscale.v1.Node.last_seen:type_name -> google.protobuf.Timestamp + 23, // 2: headscale.v1.Node.expiry:type_name -> google.protobuf.Timestamp + 24, // 3: headscale.v1.Node.pre_auth_key:type_name -> headscale.v1.PreAuthKey + 23, // 4: headscale.v1.Node.created_at:type_name -> google.protobuf.Timestamp 0, // 5: headscale.v1.Node.register_method:type_name -> headscale.v1.RegisterMethod 1, // 6: headscale.v1.RegisterNodeResponse.node:type_name -> headscale.v1.Node 1, // 7: headscale.v1.GetNodeResponse.node:type_name -> headscale.v1.Node @@ -1571,6 +1674,30 @@ func file_headscale_v1_node_proto_init() { return nil } } + file_headscale_v1_node_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackfillNodeIPsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_node_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackfillNodeIPsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1578,7 +1705,7 @@ func file_headscale_v1_node_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_headscale_v1_node_proto_rawDesc, NumEnums: 1, - NumMessages: 19, + NumMessages: 21, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/headscale/v1/preauthkey.pb.go b/gen/go/headscale/v1/preauthkey.pb.go index 35a0dfe0..c3ae2818 100644 --- a/gen/go/headscale/v1/preauthkey.pb.go +++ b/gen/go/headscale/v1/preauthkey.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/preauthkey.proto diff --git a/gen/go/headscale/v1/routes.pb.go b/gen/go/headscale/v1/routes.pb.go index d2273047..9c7475b4 100644 --- a/gen/go/headscale/v1/routes.pb.go +++ b/gen/go/headscale/v1/routes.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/routes.proto diff --git a/gen/go/headscale/v1/user.pb.go b/gen/go/headscale/v1/user.pb.go index 17cb4b54..3fcd12bf 100644 --- a/gen/go/headscale/v1/user.pb.go +++ b/gen/go/headscale/v1/user.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: headscale/v1/user.proto diff --git a/gen/openapiv2/headscale/v1/headscale.swagger.json b/gen/openapiv2/headscale/v1/headscale.swagger.json index 7fe0b696..51b4ad22 100644 --- a/gen/openapiv2/headscale/v1/headscale.swagger.json +++ b/gen/openapiv2/headscale/v1/headscale.swagger.json @@ -194,6 +194,36 @@ ] } }, + "/api/v1/node/backfillips": { + "post": { + "operationId": "HeadscaleService_BackfillNodeIPs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1BackfillNodeIPsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "confirmed", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, "/api/v1/node/register": { "post": { "operationId": "HeadscaleService_RegisterNode", @@ -886,6 +916,17 @@ } } }, + "v1BackfillNodeIPsResponse": { + "type": "object", + "properties": { + "changes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "v1CreateApiKeyRequest": { "type": "object", "properties": { diff --git a/hscontrol/app.go b/hscontrol/app.go index 6d727001..64d40ed1 100644 --- a/hscontrol/app.go +++ b/hscontrol/app.go @@ -9,7 +9,6 @@ import ( "net" "net/http" _ "net/http/pprof" //nolint - "net/netip" "os" "os/signal" "path/filepath" @@ -56,6 +55,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/dnstype" "tailscale.com/types/key" + "tailscale.com/util/dnsname" ) var ( @@ -148,7 +148,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) { return nil, err } - app.ipAlloc, err = db.NewIPAllocator(app.db, *cfg.PrefixV4, *cfg.PrefixV6) + app.ipAlloc, err = db.NewIPAllocator(app.db, cfg.PrefixV4, cfg.PrefixV6, cfg.IPAllocation) if err != nil { return nil, err } @@ -166,7 +166,15 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) { if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS // TODO(kradalby): revisit why this takes a list. - magicDNSDomains := util.GenerateMagicDNSRootDomains([]netip.Prefix{*cfg.PrefixV4, *cfg.PrefixV6}) + + var magicDNSDomains []dnsname.FQDN + if cfg.PrefixV4 != nil { + magicDNSDomains = append(magicDNSDomains, util.GenerateIPv4DNSRootDomain(*cfg.PrefixV4)...) + } + if cfg.PrefixV6 != nil { + magicDNSDomains = append(magicDNSDomains, util.GenerateIPv6DNSRootDomain(*cfg.PrefixV6)...) + } + // we might have routes already from Split DNS if app.cfg.DNSConfig.Routes == nil { app.cfg.DNSConfig.Routes = make(map[string][]*dnstype.Resolver) diff --git a/hscontrol/auth.go b/hscontrol/auth.go index 8271038c..8307d314 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/db.go b/hscontrol/db/db.go index 870ad599..b219ffe1 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "net/netip" "path/filepath" "strconv" "strings" @@ -330,6 +331,66 @@ func NewHeadscaleDatabase( return nil }, }, + { + // Replace column with IP address list with dedicated + // IP v4 and v6 column. + // Note that previously, the list _could_ contain more + // than two addresses, which should not really happen. + // In that case, the first occurence of each type will + // be kept. + ID: "2024041121742", + Migrate: func(tx *gorm.DB) error { + _ = tx.Migrator().AddColumn(&types.Node{}, "ipv4") + _ = tx.Migrator().AddColumn(&types.Node{}, "ipv6") + + type node struct { + ID uint64 `gorm:"column:id"` + Addresses string `gorm:"column:ip_addresses"` + } + + var nodes []node + + _ = tx.Raw("SELECT id, ip_addresses FROM nodes").Scan(&nodes).Error + + for _, node := range nodes { + addrs := strings.Split(node.Addresses, ",") + + if len(addrs) == 0 { + fmt.Errorf("no addresses found for node(%d)", node.ID) + } + + var v4 *netip.Addr + var v6 *netip.Addr + + for _, addrStr := range addrs { + addr, err := netip.ParseAddr(addrStr) + if err != nil { + return fmt.Errorf("parsing IP for node(%d) from database: %w", node.ID, err) + } + + if addr.Is4() && v4 == nil { + v4 = &addr + } + + if addr.Is6() && v6 == nil { + v6 = &addr + } + } + + err = tx.Save(&types.Node{ID: types.NodeID(node.ID), IPv4: v4, IPv6: v6}).Error + if err != nil { + return fmt.Errorf("saving ip addresses to new columns: %w", err) + } + } + + _ = tx.Migrator().DropColumn(&types.Node{}, "ip_addresses") + + return nil + }, + Rollback: func(tx *gorm.DB) error { + return nil + }, + }, }, ) diff --git a/hscontrol/db/ip.go b/hscontrol/db/ip.go index dc49f8af..7d06e2e8 100644 --- a/hscontrol/db/ip.go +++ b/hscontrol/db/ip.go @@ -1,13 +1,17 @@ package db import ( + "crypto/rand" + "database/sql" "errors" "fmt" + "math/big" "net/netip" "sync" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" + "github.com/rs/zerolog/log" "go4.org/netipx" "gorm.io/gorm" ) @@ -20,13 +24,16 @@ import ( type IPAllocator struct { mu sync.Mutex - prefix4 netip.Prefix - prefix6 netip.Prefix + prefix4 *netip.Prefix + prefix6 *netip.Prefix // Previous IPs handed out prev4 netip.Addr prev6 netip.Addr + // strategy used for handing out IP addresses. + strategy types.IPAllocationStrategy + // Set of all IPs handed out. // This might not be in sync with the database, // but it is more conservative. If saves to the @@ -40,40 +47,71 @@ type IPAllocator struct { // provided IPv4 and IPv6 prefix. It needs to be created // when headscale starts and needs to finish its read // transaction before any writes to the database occur. -func NewIPAllocator(db *HSDatabase, prefix4, prefix6 netip.Prefix) (*IPAllocator, error) { - var addressesSlices []string +func NewIPAllocator( + db *HSDatabase, + prefix4, prefix6 *netip.Prefix, + strategy types.IPAllocationStrategy, +) (*IPAllocator, error) { + ret := IPAllocator{ + prefix4: prefix4, + prefix6: prefix6, + + strategy: strategy, + } + + 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 // Add network and broadcast addrs to used pool so they // are not handed out to nodes. - network4, broadcast4 := util.GetIPPrefixEndpoints(prefix4) - network6, broadcast6 := util.GetIPPrefixEndpoints(prefix6) - ips.Add(network4) - ips.Add(broadcast4) - ips.Add(network6) - ips.Add(broadcast6) + if prefix4 != nil { + network4, broadcast4 := util.GetIPPrefixEndpoints(*prefix4) + ips.Add(network4) + ips.Add(broadcast4) + + // Use network as starting point, it will be used to call .Next() + // TODO(kradalby): Could potentially take all the IPs loaded from + // the database into account to start at a more "educated" location. + ret.prev4 = network4 + } + + if prefix6 != nil { + network6, broadcast6 := util.GetIPPrefixEndpoints(*prefix6) + ips.Add(network6) + ips.Add(broadcast6) + + ret.prev6 = network6 + } // 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 _, 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) + } - for _, ip := range machineAddresses { - ips.Add(ip) + ips.Add(addr) } } @@ -86,42 +124,61 @@ func NewIPAllocator(db *HSDatabase, prefix4, prefix6 netip.Prefix) (*IPAllocator ) } - return &IPAllocator{ - usedIPs: ips, + ret.usedIPs = ips - prefix4: prefix4, - prefix6: prefix6, - - // Use network as starting point, it will be used to call .Next() - // TODO(kradalby): Could potentially take all the IPs loaded from - // the database into account to start at a more "educated" location. - prev4: network4, - prev6: network6, - }, nil + 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() - v4, err := i.next(i.prev4, i.prefix4) - if err != nil { - return nil, fmt.Errorf("allocating IPv4 address: %w", err) + var err error + var ret4 *netip.Addr + var ret6 *netip.Addr + + if i.prefix4 != nil { + ret4, err = i.next(i.prev4, i.prefix4) + if err != nil { + return nil, nil, fmt.Errorf("allocating IPv4 address: %w", err) + } + i.prev4 = *ret4 } - v6, err := i.next(i.prev6, i.prefix6) - if err != nil { - return nil, fmt.Errorf("allocating IPv6 address: %w", err) + if i.prefix6 != nil { + ret6, err = i.next(i.prev6, i.prefix6) + if err != nil { + return nil, nil, fmt.Errorf("allocating IPv6 address: %w", err) + } + i.prev6 = *ret6 } - return types.NodeAddresses{*v4, *v6}, nil + return ret4, ret6, nil } var ErrCouldNotAllocateIP = errors.New("failed to allocate IP") -func (i *IPAllocator) next(prev netip.Addr, prefix netip.Prefix) (*netip.Addr, error) { - // Get the first IP in our prefix - ip := prev.Next() +func (i *IPAllocator) nextLocked(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) { + i.mu.Lock() + defer i.mu.Unlock() + + return i.next(prev, prefix) +} + +func (i *IPAllocator) next(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) { + var err error + var ip netip.Addr + + switch i.strategy { + case types.IPAllocationStrategySequential: + // Get the first IP in our prefix + ip = prev.Next() + case types.IPAllocationStrategyRandom: + ip, err = randomNext(*prefix) + if err != nil { + return nil, fmt.Errorf("getting random IP: %w", err) + } + } // TODO(kradalby): maybe this can be done less often. set, err := i.usedIPs.IPSet() @@ -136,7 +193,15 @@ func (i *IPAllocator) next(prev netip.Addr, prefix netip.Prefix) (*netip.Addr, e // Check if the IP has already been allocated. if set.Contains(ip) { - ip = ip.Next() + switch i.strategy { + case types.IPAllocationStrategySequential: + ip = ip.Next() + case types.IPAllocationStrategyRandom: + ip, err = randomNext(*prefix) + if err != nil { + return nil, fmt.Errorf("getting random IP: %w", err) + } + } continue } @@ -146,3 +211,120 @@ func (i *IPAllocator) next(prev netip.Addr, prefix netip.Prefix) (*netip.Addr, e return &ip, nil } } + +func randomNext(pfx netip.Prefix) (netip.Addr, error) { + rang := netipx.RangeOfPrefix(pfx) + fromIP, toIP := rang.From(), rang.To() + + var from, to big.Int + + from.SetBytes(fromIP.AsSlice()) + to.SetBytes(toIP.AsSlice()) + + // Find the max, this is how we can do "random range", + // get the "max" as 0 -> to - from and then add back from + // after. + tempMax := big.NewInt(0).Sub(&to, &from) + + out, err := rand.Int(rand.Reader, tempMax) + if err != nil { + return netip.Addr{}, fmt.Errorf("generating random IP: %w", err) + } + + valInRange := big.NewInt(0).Add(&from, out) + + ip, ok := netip.AddrFromSlice(valInRange.Bytes()) + if !ok { + return netip.Addr{}, fmt.Errorf("generated ip bytes are invalid ip") + } + + if !pfx.Contains(ip) { + return netip.Addr{}, fmt.Errorf( + "generated ip(%s) not in prefix(%s)", + ip.String(), + pfx.String(), + ) + } + + return ip, nil +} + +// 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 +// configuration. +// This means that if both IPv4 and IPv6 is set in the +// config, and some nodes are missing that type of IP, +// it will be added. +// If a prefix type has been removed (IPv4 or IPv6), it +// will remove the IPs in that family from the node. +func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) { + var err error + var ret []string + err = db.Write(func(tx *gorm.DB) error { + if i == nil { + return errors.New("backfilling IPs: ip allocator was nil") + } + + log.Trace().Msgf("starting to backfill IPs") + + nodes, err := ListNodes(tx) + if err != nil { + return fmt.Errorf("listing nodes to backfill IPs: %w", err) + } + + for _, node := range nodes { + log.Trace().Uint64("node.id", node.ID.Uint64()).Msg("checking if need backfill") + + changed := false + // IPv4 prefix is set, but node ip is missing, alloc + if i.prefix4 != nil && node.IPv4 == nil { + ret4, err := i.nextLocked(i.prev4, i.prefix4) + if err != nil { + return fmt.Errorf("failed to allocate ipv4 for node(%d): %w", node.ID, err) + } + + node.IPv4 = ret4 + changed = true + ret = append(ret, fmt.Sprintf("assigned IPv4 %q to Node(%d) %q", ret4.String(), node.ID, node.Hostname)) + } + + // IPv6 prefix is set, but node ip is missing, alloc + if i.prefix6 != nil && node.IPv6 == nil { + ret6, err := i.nextLocked(i.prev6, i.prefix6) + if err != nil { + return fmt.Errorf("failed to allocate ipv6 for node(%d): %w", node.ID, err) + } + + node.IPv6 = ret6 + changed = true + ret = append(ret, fmt.Sprintf("assigned IPv6 %q to Node(%d) %q", ret6.String(), node.ID, node.Hostname)) + } + + // IPv4 prefix is not set, but node has IP, remove + if i.prefix4 == nil && node.IPv4 != nil { + ret = append(ret, fmt.Sprintf("removing IPv4 %q from Node(%d) %q", node.IPv4.String(), node.ID, node.Hostname)) + node.IPv4 = nil + changed = true + } + + // IPv6 prefix is not set, but node has IP, remove + if i.prefix6 == nil && node.IPv6 != nil { + ret = append(ret, fmt.Sprintf("removing IPv6 %q from Node(%d) %q", node.IPv6.String(), node.ID, node.Hostname)) + node.IPv6 = nil + changed = true + } + + if changed { + err := tx.Save(node).Error + if err != nil { + return fmt.Errorf("saving node(%d) after adding IPs: %w", node.ID, err) + } + } + } + + return nil + }) + + return ret, err +} diff --git a/hscontrol/db/ip_test.go b/hscontrol/db/ip_test.go index 17f39c81..a651476c 100644 --- a/hscontrol/db/ip_test.go +++ b/hscontrol/db/ip_test.go @@ -1,49 +1,41 @@ package db import ( + "database/sql" + "fmt" "net/netip" - "os" + "strings" "testing" "github.com/davecgh/go-spew/spew" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" ) -func TestIPAllocator(t *testing.T) { - mpp := func(pref string) netip.Prefix { - return netip.MustParsePrefix(pref) - } - na := func(pref string) netip.Addr { - return netip.MustParseAddr(pref) - } - newDb := func() *HSDatabase { - tmpDir, err := os.MkdirTemp("", "headscale-db-test-*") - if err != nil { - t.Fatalf("creating temp dir: %s", err) - } - db, _ = NewHeadscaleDatabase( - types.DatabaseConfig{ - Type: "sqlite3", - Sqlite: types.SqliteConfig{ - Path: tmpDir + "/headscale_test.db", - }, - }, - "", - ) - - return db - } +var mpp = func(pref string) *netip.Prefix { + p := netip.MustParsePrefix(pref) + return &p +} +var na = func(pref string) netip.Addr { + return netip.MustParseAddr(pref) +} +var nap = func(pref string) *netip.Addr { + n := na(pref) + return &n +} +func TestIPAllocatorSequential(t *testing.T) { tests := []struct { name string dbFunc func() *HSDatabase - prefix4 netip.Prefix - prefix6 netip.Prefix + prefix4 *netip.Prefix + prefix6 *netip.Prefix getCount int - want []types.NodeAddresses + want4 []netip.Addr + want6 []netip.Addr }{ { name: "simple", @@ -56,23 +48,49 @@ 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"), + }, + }, + { + name: "simple-v4", + dbFunc: func() *HSDatabase { + return nil + }, + + prefix4: mpp("100.64.0.0/10"), + + getCount: 1, + + want4: []netip.Addr{ + na("100.64.0.1"), + }, + }, + { + name: "simple-v6", + dbFunc: func() *HSDatabase { + return nil + }, + + prefix6: mpp("fd7a:115c:a1e0::/48"), + + getCount: 1, + + want6: []netip.Addr{ + na("fd7a:115c:a1e0::1"), }, }, { name: "simple-with-db", dbFunc: func() *HSDatabase { - db := newDb() + db := dbForTest(t, "simple-with-db") db.DB.Save(&types.Node{ - IPAddresses: types.NodeAddresses{ - na("100.64.0.1"), - na("fd7a:115c:a1e0::1"), - }, + IPv4: nap("100.64.0.1"), + IPv6: nap("fd7a:115c:a1e0::1"), }) return db @@ -83,23 +101,21 @@ 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"), }, }, { name: "before-after-free-middle-in-db", dbFunc: func() *HSDatabase { - db := newDb() + db := dbForTest(t, "before-after-free-middle-in-db") db.DB.Save(&types.Node{ - IPAddresses: types.NodeAddresses{ - na("100.64.0.2"), - na("fd7a:115c:a1e0::2"), - }, + IPv4: nap("100.64.0.2"), + IPv6: nap("fd7a:115c:a1e0::2"), }) return db @@ -110,15 +126,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"), }, }, } @@ -127,24 +141,347 @@ func TestIPAllocator(t *testing.T) { t.Run(tt.name, func(t *testing.T) { db := tt.dbFunc() - alloc, _ := NewIPAllocator(db, tt.prefix4, tt.prefix6) + alloc, _ := NewIPAllocator( + db, + tt.prefix4, + tt.prefix6, + types.IPAllocationStrategySequential, + ) spew.Dump(alloc) - t.Logf("prefixes: %q, %q", tt.prefix4.String(), tt.prefix6.String()) - - 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) + } + }) + } +} + +func TestIPAllocatorRandom(t *testing.T) { + tests := []struct { + name string + dbFunc func() *HSDatabase + + getCount int + + prefix4 *netip.Prefix + prefix6 *netip.Prefix + want4 bool + want6 bool + }{ + { + name: "simple", + dbFunc: func() *HSDatabase { + return nil + }, + + prefix4: mpp("100.64.0.0/10"), + prefix6: mpp("fd7a:115c:a1e0::/48"), + + getCount: 1, + + want4: true, + want6: true, + }, + { + name: "simple-v4", + dbFunc: func() *HSDatabase { + return nil + }, + + prefix4: mpp("100.64.0.0/10"), + + getCount: 1, + + want4: true, + want6: false, + }, + { + name: "simple-v6", + dbFunc: func() *HSDatabase { + return nil + }, + + prefix6: mpp("fd7a:115c:a1e0::/48"), + + getCount: 1, + + want4: false, + want6: true, + }, + { + name: "generate-lots-of-random", + dbFunc: func() *HSDatabase { + return nil + }, + + prefix4: mpp("100.64.0.0/10"), + prefix6: mpp("fd7a:115c:a1e0::/48"), + + getCount: 1000, + + want4: true, + want6: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := tt.dbFunc() + + alloc, _ := NewIPAllocator(db, tt.prefix4, tt.prefix6, types.IPAllocationStrategyRandom) + + spew.Dump(alloc) + + for range tt.getCount { + got4, got6, err := alloc.Next() + if err != nil { + t.Fatalf("allocating next IP: %s", err) + } + + t.Logf("addrs ipv4: %v, ipv6: %v", got4, got6) + + if tt.want4 { + if got4 == nil { + t.Fatalf("expected ipv4 addr, got nil") + } + } + + if tt.want6 { + if got6 == nil { + t.Fatalf("expected ipv4 addr, got nil") + } + } + } + }) + } +} + +func TestBackfillIPAddresses(t *testing.T) { + fullNodeP := func(i int) *types.Node { + v4 := fmt.Sprintf("100.64.0.%d", i) + v6 := fmt.Sprintf("fd7a:115c:a1e0::%d", i) + return &types.Node{ + IPv4DatabaseField: sql.NullString{ + Valid: true, + String: v4, + }, + IPv4: nap(v4), + IPv6DatabaseField: sql.NullString{ + Valid: true, + String: v6, + }, + IPv6: nap(v6), + } + } + tests := []struct { + name string + dbFunc func() *HSDatabase + + prefix4 *netip.Prefix + prefix6 *netip.Prefix + want types.Nodes + }{ + { + name: "simple-backfill-ipv6", + dbFunc: func() *HSDatabase { + db := dbForTest(t, "simple-backfill-ipv6") + + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.1"), + }) + + return db + }, + + prefix4: mpp("100.64.0.0/10"), + prefix6: mpp("fd7a:115c:a1e0::/48"), + + want: types.Nodes{ + &types.Node{ + IPv4DatabaseField: sql.NullString{ + Valid: true, + String: "100.64.0.1", + }, + IPv4: nap("100.64.0.1"), + IPv6DatabaseField: sql.NullString{ + Valid: true, + String: "fd7a:115c:a1e0::1", + }, + IPv6: nap("fd7a:115c:a1e0::1"), + }, + }, + }, + { + name: "simple-backfill-ipv4", + dbFunc: func() *HSDatabase { + db := dbForTest(t, "simple-backfill-ipv4") + + db.DB.Save(&types.Node{ + IPv6: nap("fd7a:115c:a1e0::1"), + }) + + return db + }, + + prefix4: mpp("100.64.0.0/10"), + prefix6: mpp("fd7a:115c:a1e0::/48"), + + want: types.Nodes{ + &types.Node{ + IPv4DatabaseField: sql.NullString{ + Valid: true, + String: "100.64.0.1", + }, + IPv4: nap("100.64.0.1"), + IPv6DatabaseField: sql.NullString{ + Valid: true, + String: "fd7a:115c:a1e0::1", + }, + IPv6: nap("fd7a:115c:a1e0::1"), + }, + }, + }, + { + name: "simple-backfill-remove-ipv6", + dbFunc: func() *HSDatabase { + db := dbForTest(t, "simple-backfill-remove-ipv6") + + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.1"), + IPv6: nap("fd7a:115c:a1e0::1"), + }) + + return db + }, + + prefix4: mpp("100.64.0.0/10"), + + want: types.Nodes{ + &types.Node{ + IPv4DatabaseField: sql.NullString{ + Valid: true, + String: "100.64.0.1", + }, + IPv4: nap("100.64.0.1"), + }, + }, + }, + { + name: "simple-backfill-remove-ipv4", + dbFunc: func() *HSDatabase { + db := dbForTest(t, "simple-backfill-remove-ipv4") + + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.1"), + IPv6: nap("fd7a:115c:a1e0::1"), + }) + + return db + }, + + prefix6: mpp("fd7a:115c:a1e0::/48"), + + want: types.Nodes{ + &types.Node{ + IPv6DatabaseField: sql.NullString{ + Valid: true, + String: "fd7a:115c:a1e0::1", + }, + IPv6: nap("fd7a:115c:a1e0::1"), + }, + }, + }, + { + name: "multi-backfill-ipv6", + dbFunc: func() *HSDatabase { + db := dbForTest(t, "simple-backfill-ipv6") + + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.1"), + }) + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.2"), + }) + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.3"), + }) + db.DB.Save(&types.Node{ + IPv4: nap("100.64.0.4"), + }) + + return db + }, + + prefix4: mpp("100.64.0.0/10"), + prefix6: mpp("fd7a:115c:a1e0::/48"), + + want: types.Nodes{ + fullNodeP(1), + fullNodeP(2), + fullNodeP(3), + fullNodeP(4), + }, + }, + } + + comps := append(util.Comparers, cmpopts.IgnoreFields(types.Node{}, + "ID", + "MachineKeyDatabaseField", + "NodeKeyDatabaseField", + "DiscoKeyDatabaseField", + "Endpoints", + "HostinfoDatabaseField", + "Hostinfo", + "Routes", + "CreatedAt", + "UpdatedAt", + )) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := tt.dbFunc() + + alloc, err := NewIPAllocator(db, tt.prefix4, tt.prefix6, types.IPAllocationStrategySequential) + if err != nil { + t.Fatalf("failed to set up ip alloc: %s", err) + } + + logs, err := db.BackfillNodeIPs(alloc) + if err != nil { + t.Fatalf("failed to backfill: %s", err) + } + + t.Logf("backfill log: \n%s", strings.Join(logs, "\n")) + + got, err := db.ListNodes() + if err != nil { + t.Fatalf("failed to get nodes: %s", err) + } + + if diff := cmp.Diff(tt.want, got, comps...); diff != "" { + t.Errorf("Backfill unexpected result (-want +got):\n%s", diff) } }) } diff --git a/hscontrol/db/node.go b/hscontrol/db/node.go index 4f8688c1..b3977214 100644 --- a/hscontrol/db/node.go +++ b/hscontrol/db/node.go @@ -5,7 +5,6 @@ import ( "fmt" "net/netip" "sort" - "strings" "time" "github.com/juanfont/headscale/hscontrol/types" @@ -294,7 +293,8 @@ func RegisterNodeFromAuthCallback( userName string, nodeExpiry *time.Time, registrationMethod string, - addrs types.NodeAddresses, + ipv4 *netip.Addr, + ipv6 *netip.Addr, ) (*types.Node, error) { log.Debug(). Str("machine_key", mkey.ShortString()). @@ -330,7 +330,7 @@ func RegisterNodeFromAuthCallback( node, err := RegisterNode( tx, registrationNode, - addrs, + ipv4, ipv6, ) if err == nil { @@ -346,14 +346,14 @@ func RegisterNodeFromAuthCallback( return nil, ErrNodeNotFoundRegistrationCache } -func (hsdb *HSDatabase) RegisterNode(node types.Node, addrs types.NodeAddresses) (*types.Node, error) { +func (hsdb *HSDatabase) RegisterNode(node types.Node, ipv4 *netip.Addr, ipv6 *netip.Addr) (*types.Node, error) { return Write(hsdb.DB, func(tx *gorm.DB) (*types.Node, error) { - return RegisterNode(tx, node, addrs) + return RegisterNode(tx, node, ipv4, ipv6) }) } // RegisterNode is executed from the CLI to register a new Node using its MachineKey. -func RegisterNode(tx *gorm.DB, node types.Node, addrs types.NodeAddresses) (*types.Node, error) { +func RegisterNode(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Addr) (*types.Node, error) { log.Debug(). Str("node", node.Hostname). Str("machine_key", node.MachineKey.ShortString()). @@ -361,10 +361,10 @@ func RegisterNode(tx *gorm.DB, node types.Node, addrs types.NodeAddresses) (*typ Str("user", node.User.Name). Msg("Registering node") - // If the node exists and we had already IPs for it, we just save it + // If the node exists and it already has IP(s), we just save it // so we store the node.Expire and node.Nodekey that has been set when // adding it to the registrationCache - if len(node.IPAddresses) > 0 { + if node.IPv4 != nil || node.IPv6 != nil { if err := tx.Save(&node).Error; err != nil { return nil, fmt.Errorf("failed register existing node in the database: %w", err) } @@ -380,7 +380,8 @@ func RegisterNode(tx *gorm.DB, node types.Node, addrs types.NodeAddresses) (*typ return &node, nil } - node.IPAddresses = addrs + node.IPv4 = ipv4 + node.IPv6 = ipv6 if err := tx.Save(&node).Error; err != nil { return nil, fmt.Errorf("failed register(save) node in the database: %w", err) @@ -389,7 +390,6 @@ func RegisterNode(tx *gorm.DB, node types.Node, addrs types.NodeAddresses) (*typ log.Trace(). Caller(). Str("node", node.Hostname). - Str("ip", strings.Join(addrs.StringSlice(), ",")). Msg("Node registered with the database") return &node, nil diff --git a/hscontrol/db/node_test.go b/hscontrol/db/node_test.go index 0dbe7688..84116f25 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 a94e1a88..bc3f88a5 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 d5a1854e..a24dcead 100644 --- a/hscontrol/grpcv1.go +++ b/hscontrol/grpcv1.go @@ -3,6 +3,7 @@ package hscontrol import ( "context" + "errors" "fmt" "sort" "strings" @@ -195,7 +196,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 +209,7 @@ func (api headscaleV1APIServer) RegisterNode( request.GetUser(), nil, util.RegisterMethodCLI, - addrs, + ipv4, ipv6, ) }) if err != nil { @@ -468,6 +469,24 @@ func (api headscaleV1APIServer) MoveNode( return &v1.MoveNodeResponse{Node: node.Proto()}, nil } +func (api headscaleV1APIServer) BackfillNodeIPs( + ctx context.Context, + request *v1.BackfillNodeIPsRequest, +) (*v1.BackfillNodeIPsResponse, error) { + log.Trace().Msg("Backfill called") + + if !request.Confirmed { + return nil, errors.New("not confirmed, aborting") + } + + changes, err := api.h.db.BackfillNodeIPs(api.h.ipAlloc) + if err != nil { + return nil, err + } + + return &v1.BackfillNodeIPsResponse{Changes: changes}, nil +} + func (api headscaleV1APIServer) GetRoutes( ctx context.Context, request *v1.GetRoutesRequest, diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index 93ab1f71..fe8af4d3 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 3f4d6892..f6248470 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 97d12e86..ac39d35e 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 e79d9dc5..229f0f88 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 2ac1b56c..0680ce2f 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 a4eee01e..0f6158c6 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 db1a0dd3..417ed1d1 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/config.go b/hscontrol/types/config.go index 4e4b9a61..5cc11668 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -31,6 +31,13 @@ var errOidcMutuallyExclusive = errors.New( "oidc_client_secret and oidc_client_secret_path are mutually exclusive", ) +type IPAllocationStrategy string + +const ( + IPAllocationStrategySequential IPAllocationStrategy = "sequential" + IPAllocationStrategyRandom IPAllocationStrategy = "random" +) + // Config contains the initial Headscale configuration. type Config struct { ServerURL string @@ -42,6 +49,7 @@ type Config struct { NodeUpdateCheckInterval time.Duration PrefixV4 *netip.Prefix PrefixV6 *netip.Prefix + IPAllocation IPAllocationStrategy NoisePrivateKeyPath string BaseDomain string Log LogConfig @@ -230,6 +238,8 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("tuning.batch_change_delay", "800ms") viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30) + viper.SetDefault("prefixes.allocation", IPAllocationStrategySequential) + if IsCLIConfigured() { return nil } @@ -579,18 +589,16 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { return nil, "" } -func Prefixes() (*netip.Prefix, *netip.Prefix, error) { +func PrefixV4() (*netip.Prefix, error) { prefixV4Str := viper.GetString("prefixes.v4") - prefixV6Str := viper.GetString("prefixes.v6") + + if prefixV4Str == "" { + return nil, nil + } prefixV4, err := netip.ParsePrefix(prefixV4Str) if err != nil { - return nil, nil, err - } - - prefixV6, err := netip.ParsePrefix(prefixV6Str) - if err != nil { - return nil, nil, err + return nil, fmt.Errorf("parsing IPv4 prefix from config: %w", err) } builder := netipx.IPSetBuilder{} @@ -603,13 +611,33 @@ func Prefixes() (*netip.Prefix, *netip.Prefix, error) { prefixV4Str, tsaddr.CGNATRange()) } + return &prefixV4, nil +} + +func PrefixV6() (*netip.Prefix, error) { + prefixV6Str := viper.GetString("prefixes.v6") + + if prefixV6Str == "" { + return nil, nil + } + + prefixV6, err := netip.ParsePrefix(prefixV6Str) + if err != nil { + return nil, fmt.Errorf("parsing IPv6 prefix from config: %w", err) + } + + builder := netipx.IPSetBuilder{} + builder.AddPrefix(tsaddr.CGNATRange()) + builder.AddPrefix(tsaddr.TailscaleULARange()) + ipSet, _ := builder.IPSet() + if !ipSet.ContainsPrefix(prefixV6) { log.Warn(). Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.", prefixV6Str, tsaddr.TailscaleULARange()) } - return &prefixV4, &prefixV6, nil + return &prefixV6, nil } func GetHeadscaleConfig() (*Config, error) { @@ -624,11 +652,27 @@ func GetHeadscaleConfig() (*Config, error) { }, nil } - prefix4, prefix6, err := Prefixes() + prefix4, err := PrefixV4() if err != nil { return nil, err } + prefix6, err := PrefixV6() + if err != nil { + return nil, err + } + + allocStr := viper.GetString("prefixes.allocation") + var alloc IPAllocationStrategy + switch allocStr { + case string(IPAllocationStrategySequential): + alloc = IPAllocationStrategySequential + case string(IPAllocationStrategyRandom): + alloc = IPAllocationStrategyRandom + default: + log.Fatal().Msgf("config error, prefixes.allocation is set to %s, which is not a valid strategy, allowed options: %s, %s", allocStr, IPAllocationStrategySequential, IPAllocationStrategyRandom) + } + dnsConfig, baseDomain := GetDNSConfig() derpConfig := GetDERPConfig() logConfig := GetLogTailConfig() @@ -655,8 +699,9 @@ func GetHeadscaleConfig() (*Config, error) { GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"), DisableUpdateCheck: viper.GetBool("disable_check_updates"), - PrefixV4: prefix4, - PrefixV6: prefix6, + PrefixV4: prefix4, + PrefixV6: prefix6, + IPAllocation: IPAllocationStrategy(alloc), NoisePrivateKeyPath: util.AbsolutePathFromConfigPath( viper.GetString("noise.private_key_path"), diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index 2d6c6310..0e30bd9e 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,89 +133,6 @@ type ( Nodes []*Node ) -type NodeAddresses []netip.Addr - -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 na[index1].Compare(na[index2]) < 0 - }) -} - -func (na NodeAddresses) StringSlice() []string { - na.Sort() - strSlice := make([]string, 0, len(na)) - for _, addr := range na { - strSlice = append(strSlice, addr.String()) - } - - return strSlice -} - -func (na NodeAddresses) Prefixes() []netip.Prefix { - addrs := []netip.Prefix{} - for _, nodeAddress := range na { - ip := netip.PrefixFrom(nodeAddress, nodeAddress.BitLen()) - addrs = append(addrs, ip) - } - - return addrs -} - -func (na NodeAddresses) InIPSet(set *netipx.IPSet) bool { - for _, nodeAddr := range na { - if set.Contains(nodeAddr) { - return true - } - } - - return false -} - -// AppendToIPSet adds the individual ips in NodeAddresses to a -// given netipx.IPSetBuilder. -func (na NodeAddresses) AppendToIPSet(build *netipx.IPSetBuilder) { - for _, ip := range na { - 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 @@ -224,8 +151,65 @@ func (node *Node) IsEphemeral() bool { return node.AuthKey != nil && node.AuthKey.Ephemeral } +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 ret +} + +func (node *Node) Prefixes() []netip.Prefix { + addrs := []netip.Prefix{} + for _, nodeAddress := range node.IPs() { + ip := netip.PrefixFrom(nodeAddress, nodeAddress.BitLen()) + addrs = append(addrs, ip) + } + + return addrs +} + +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 + } + } + + return false +} + +// AppendToIPSet adds the individual ips in NodeAddresses to a +// given netipx.IPSetBuilder. +func (node *Node) AppendToIPSet(build *netipx.IPSetBuilder) { + for _, ip := range node.IPs() { + build.Add(ip) + } +} + 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 712a839e..157be89e 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.go b/hscontrol/util/dns.go index c6bd2b69..ab3c90b7 100644 --- a/hscontrol/util/dns.go +++ b/hscontrol/util/dns.go @@ -103,33 +103,7 @@ func CheckForFQDNRules(name string) error { // From the netmask we can find out the wildcard bits (the bits that are not set in the netmask). // This allows us to then calculate the subnets included in the subsequent class block and generate the entries. -func GenerateMagicDNSRootDomains(ipPrefixes []netip.Prefix) []dnsname.FQDN { - fqdns := make([]dnsname.FQDN, 0, len(ipPrefixes)) - for _, ipPrefix := range ipPrefixes { - var generateDNSRoot func(netip.Prefix) []dnsname.FQDN - switch ipPrefix.Addr().BitLen() { - case ipv4AddressLength: - generateDNSRoot = generateIPv4DNSRootDomain - - case ipv6AddressLength: - generateDNSRoot = generateIPv6DNSRootDomain - - default: - panic( - fmt.Sprintf( - "unsupported IP version with address length %d", - ipPrefix.Addr().BitLen(), - ), - ) - } - - fqdns = append(fqdns, generateDNSRoot(ipPrefix)...) - } - - return fqdns -} - -func generateIPv4DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN { +func GenerateIPv4DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN { // Conversion to the std lib net.IPnet, a bit easier to operate netRange := netipx.PrefixIPNet(ipPrefix) maskBits, _ := netRange.Mask.Size() @@ -165,7 +139,27 @@ func generateIPv4DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN { return fqdns } -func generateIPv6DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN { +// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`. +// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS +// server (listening in 100.100.100.100 udp/53) should be used for. +// +// Tailscale.com includes in the list: +// - the `BaseDomain` of the user +// - the reverse DNS entry for IPv6 (0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa., see below more on IPv6) +// - the reverse DNS entries for the IPv4 subnets covered by the user's `IPPrefix`. +// In the public SaaS this is [64-127].100.in-addr.arpa. +// +// The main purpose of this function is then generating the list of IPv4 entries. For the 100.64.0.0/10, this +// is clear, and could be hardcoded. But we are allowing any range as `IPPrefix`, so we need to find out the +// subnets when we have 172.16.0.0/16 (i.e., [0-255].16.172.in-addr.arpa.), or any other subnet. +// +// How IN-ADDR.ARPA domains work is defined in RFC1035 (section 3.5). Tailscale.com seems to adhere to this, +// and do not make use of RFC2317 ("Classless IN-ADDR.ARPA delegation") - hence generating the entries for the next +// class block only. + +// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask). +// This allows us to then calculate the subnets included in the subsequent class block and generate the entries. +func GenerateIPv6DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN { const nibbleLen = 4 maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size() diff --git a/hscontrol/util/dns_test.go b/hscontrol/util/dns_test.go index 9d9b08b3..2559cae6 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 { diff --git a/integration/general_test.go b/integration/general_test.go index e9f9abea..ffd209d8 100644 --- a/integration/general_test.go +++ b/integration/general_test.go @@ -9,6 +9,7 @@ import ( "time" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/tsic" "github.com/rs/zerolog/log" @@ -39,6 +40,7 @@ func TestPingAllByIP(t *testing.T) { hsic.WithEmbeddedDERPServerOnly(), hsic.WithTLS(), hsic.WithHostnameAsServerURL(), + hsic.WithIPAllocationStrategy(types.IPAllocationStrategyRandom), ) assertNoErrHeadscaleEnv(t, err) diff --git a/integration/hsic/config.go b/integration/hsic/config.go index 64e6e6eb..d66fd34c 100644 --- a/integration/hsic/config.go +++ b/integration/hsic/config.go @@ -1,5 +1,7 @@ package hsic +import "github.com/juanfont/headscale/hscontrol/types" + // const ( // defaultEphemeralNodeInactivityTimeout = time.Second * 30 // defaultNodeUpdateCheckInterval = time.Second * 10 @@ -129,5 +131,9 @@ func DefaultConfigEnv() map[string]string { "HEADSCALE_DERP_URLS": "https://controlplane.tailscale.com/derpmap/default", "HEADSCALE_DERP_AUTO_UPDATE_ENABLED": "false", "HEADSCALE_DERP_UPDATE_FREQUENCY": "1m", + + // a bunch of tests (ACL/Policy) rely on predicable IP alloc, + // so ensure the sequential alloc is used by default. + "HEADSCALE_PREFIXES_ALLOCATION": string(types.IPAllocationStrategySequential), } } diff --git a/integration/hsic/hsic.go b/integration/hsic/hsic.go index 2bd60954..de4ec41f 100644 --- a/integration/hsic/hsic.go +++ b/integration/hsic/hsic.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/policy" + "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/integrationutil" @@ -173,6 +174,13 @@ func WithPostgres() Option { } } +// WithIPAllocationStrategy sets the tests IP Allocation strategy. +func WithIPAllocationStrategy(strat types.IPAllocationStrategy) Option { + return func(hsic *HeadscaleInContainer) { + hsic.env["HEADSCALE_PREFIXES_ALLOCATION"] = string(strat) + } +} + // WithEmbeddedDERPServerOnly configures Headscale to start // and only use the embedded DERP server. // It requires WithTLS and WithHostnameAsServerURL to be diff --git a/proto/headscale/v1/headscale.proto b/proto/headscale/v1/headscale.proto index f8cc596f..1ccc7029 100644 --- a/proto/headscale/v1/headscale.proto +++ b/proto/headscale/v1/headscale.proto @@ -123,6 +123,13 @@ service HeadscaleService { post: "/api/v1/node/{node_id}/user" }; } + + rpc BackfillNodeIPs(BackfillNodeIPsRequest) returns (BackfillNodeIPsResponse) { + option (google.api.http) = { + post: "/api/v1/node/backfillips" + }; + } + // --- Node end --- // --- Route start --- diff --git a/proto/headscale/v1/node.proto b/proto/headscale/v1/node.proto index a9551530..26fe73c7 100644 --- a/proto/headscale/v1/node.proto +++ b/proto/headscale/v1/node.proto @@ -126,3 +126,11 @@ message DebugCreateNodeRequest { message DebugCreateNodeResponse { Node node = 1; } + +message BackfillNodeIPsRequest { + bool confirmed = 1; +} + +message BackfillNodeIPsResponse { + repeated string changes = 1; +}