Migrate IP fields in database to dedicated columns (#1869)

This commit is contained in:
Kristoffer Dalby 2024-04-17 07:03:06 +02:00 committed by GitHub
parent 85cef84e17
commit 2ce23df45a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 1885 additions and 1055 deletions

View file

@ -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) - `/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) - 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` - `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 ### 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) - 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) - 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 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) ## 0.22.3 (2023-05-12)

View file

@ -97,6 +97,8 @@ func init() {
tagCmd.Flags(). tagCmd.Flags().
StringSliceP("tags", "t", []string{}, "List of tags to add to the node") StringSliceP("tags", "t", []string{}, "List of tags to add to the node")
nodeCmd.AddCommand(tagCmd) nodeCmd.AddCommand(tagCmd)
nodeCmd.AddCommand(backfillNodeIPsCmd)
} }
var nodeCmd = &cobra.Command{ 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( func nodesToPtables(
currentUser string, currentUser string,
showTags bool, showTags bool,

View file

@ -61,6 +61,11 @@ prefixes:
v6: fd7a:115c:a1e0::/48 v6: fd7a:115c:a1e0::/48
v4: 100.64.0.0/10 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 # DERP is a relay system that Tailscale uses when a direct
# connection cannot be established. # connection cannot be established.
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp # https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/apikey.proto // source: headscale/v1/apikey.proto

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/device.proto // source: headscale/v1/device.proto

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/headscale.proto // 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x73, 0x65, 0x72, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c,
0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e,
0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63,
0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x73, 0x70,
0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x7c, 0x0a, 0x0b, 0x45, 0x6e, 0x61, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61,
0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x66,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x69, 0x6c, 0x6c, 0x69, 0x70, 0x73, 0x12, 0x64, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75,
0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0xd3, 0xe4, 0x93, 0x02, 0x22, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61,
0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x7c, 0x0a, 0x0b,
0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c,
0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 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, 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, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22,
0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69,
0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x64, 0x7d, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x7f, 0x0a, 0x0d, 0x47, 0x65, 0x73, 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69,
0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74,
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x7f, 0x0a,
0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x22,
0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x61, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52,
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x75, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x75,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c,
0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x64, 0x7d, 0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69,
0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74,
0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 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, 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, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70,
0x69, 0x6b, 0x65, 0x79, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3,
0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 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, 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, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61,
0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65,
0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x6a, 0x0a, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20,
0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x20, 0x2e, 0x68, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x12, 0x6a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x76, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70,
0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x76, 0x0a, 0x0c,
0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 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, 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, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x7b, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x7d, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x61, 0x70,
0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x7b, 0x70, 0x72, 0x65,
0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x66, 0x69, 0x78, 0x7d, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x74, 0x6f, 0x33, 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{}{ 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 (*RenameNodeRequest)(nil), // 14: headscale.v1.RenameNodeRequest
(*ListNodesRequest)(nil), // 15: headscale.v1.ListNodesRequest (*ListNodesRequest)(nil), // 15: headscale.v1.ListNodesRequest
(*MoveNodeRequest)(nil), // 16: headscale.v1.MoveNodeRequest (*MoveNodeRequest)(nil), // 16: headscale.v1.MoveNodeRequest
(*GetRoutesRequest)(nil), // 17: headscale.v1.GetRoutesRequest (*BackfillNodeIPsRequest)(nil), // 17: headscale.v1.BackfillNodeIPsRequest
(*EnableRouteRequest)(nil), // 18: headscale.v1.EnableRouteRequest (*GetRoutesRequest)(nil), // 18: headscale.v1.GetRoutesRequest
(*DisableRouteRequest)(nil), // 19: headscale.v1.DisableRouteRequest (*EnableRouteRequest)(nil), // 19: headscale.v1.EnableRouteRequest
(*GetNodeRoutesRequest)(nil), // 20: headscale.v1.GetNodeRoutesRequest (*DisableRouteRequest)(nil), // 20: headscale.v1.DisableRouteRequest
(*DeleteRouteRequest)(nil), // 21: headscale.v1.DeleteRouteRequest (*GetNodeRoutesRequest)(nil), // 21: headscale.v1.GetNodeRoutesRequest
(*CreateApiKeyRequest)(nil), // 22: headscale.v1.CreateApiKeyRequest (*DeleteRouteRequest)(nil), // 22: headscale.v1.DeleteRouteRequest
(*ExpireApiKeyRequest)(nil), // 23: headscale.v1.ExpireApiKeyRequest (*CreateApiKeyRequest)(nil), // 23: headscale.v1.CreateApiKeyRequest
(*ListApiKeysRequest)(nil), // 24: headscale.v1.ListApiKeysRequest (*ExpireApiKeyRequest)(nil), // 24: headscale.v1.ExpireApiKeyRequest
(*DeleteApiKeyRequest)(nil), // 25: headscale.v1.DeleteApiKeyRequest (*ListApiKeysRequest)(nil), // 25: headscale.v1.ListApiKeysRequest
(*GetUserResponse)(nil), // 26: headscale.v1.GetUserResponse (*DeleteApiKeyRequest)(nil), // 26: headscale.v1.DeleteApiKeyRequest
(*CreateUserResponse)(nil), // 27: headscale.v1.CreateUserResponse (*GetUserResponse)(nil), // 27: headscale.v1.GetUserResponse
(*RenameUserResponse)(nil), // 28: headscale.v1.RenameUserResponse (*CreateUserResponse)(nil), // 28: headscale.v1.CreateUserResponse
(*DeleteUserResponse)(nil), // 29: headscale.v1.DeleteUserResponse (*RenameUserResponse)(nil), // 29: headscale.v1.RenameUserResponse
(*ListUsersResponse)(nil), // 30: headscale.v1.ListUsersResponse (*DeleteUserResponse)(nil), // 30: headscale.v1.DeleteUserResponse
(*CreatePreAuthKeyResponse)(nil), // 31: headscale.v1.CreatePreAuthKeyResponse (*ListUsersResponse)(nil), // 31: headscale.v1.ListUsersResponse
(*ExpirePreAuthKeyResponse)(nil), // 32: headscale.v1.ExpirePreAuthKeyResponse (*CreatePreAuthKeyResponse)(nil), // 32: headscale.v1.CreatePreAuthKeyResponse
(*ListPreAuthKeysResponse)(nil), // 33: headscale.v1.ListPreAuthKeysResponse (*ExpirePreAuthKeyResponse)(nil), // 33: headscale.v1.ExpirePreAuthKeyResponse
(*DebugCreateNodeResponse)(nil), // 34: headscale.v1.DebugCreateNodeResponse (*ListPreAuthKeysResponse)(nil), // 34: headscale.v1.ListPreAuthKeysResponse
(*GetNodeResponse)(nil), // 35: headscale.v1.GetNodeResponse (*DebugCreateNodeResponse)(nil), // 35: headscale.v1.DebugCreateNodeResponse
(*SetTagsResponse)(nil), // 36: headscale.v1.SetTagsResponse (*GetNodeResponse)(nil), // 36: headscale.v1.GetNodeResponse
(*RegisterNodeResponse)(nil), // 37: headscale.v1.RegisterNodeResponse (*SetTagsResponse)(nil), // 37: headscale.v1.SetTagsResponse
(*DeleteNodeResponse)(nil), // 38: headscale.v1.DeleteNodeResponse (*RegisterNodeResponse)(nil), // 38: headscale.v1.RegisterNodeResponse
(*ExpireNodeResponse)(nil), // 39: headscale.v1.ExpireNodeResponse (*DeleteNodeResponse)(nil), // 39: headscale.v1.DeleteNodeResponse
(*RenameNodeResponse)(nil), // 40: headscale.v1.RenameNodeResponse (*ExpireNodeResponse)(nil), // 40: headscale.v1.ExpireNodeResponse
(*ListNodesResponse)(nil), // 41: headscale.v1.ListNodesResponse (*RenameNodeResponse)(nil), // 41: headscale.v1.RenameNodeResponse
(*MoveNodeResponse)(nil), // 42: headscale.v1.MoveNodeResponse (*ListNodesResponse)(nil), // 42: headscale.v1.ListNodesResponse
(*GetRoutesResponse)(nil), // 43: headscale.v1.GetRoutesResponse (*MoveNodeResponse)(nil), // 43: headscale.v1.MoveNodeResponse
(*EnableRouteResponse)(nil), // 44: headscale.v1.EnableRouteResponse (*BackfillNodeIPsResponse)(nil), // 44: headscale.v1.BackfillNodeIPsResponse
(*DisableRouteResponse)(nil), // 45: headscale.v1.DisableRouteResponse (*GetRoutesResponse)(nil), // 45: headscale.v1.GetRoutesResponse
(*GetNodeRoutesResponse)(nil), // 46: headscale.v1.GetNodeRoutesResponse (*EnableRouteResponse)(nil), // 46: headscale.v1.EnableRouteResponse
(*DeleteRouteResponse)(nil), // 47: headscale.v1.DeleteRouteResponse (*DisableRouteResponse)(nil), // 47: headscale.v1.DisableRouteResponse
(*CreateApiKeyResponse)(nil), // 48: headscale.v1.CreateApiKeyResponse (*GetNodeRoutesResponse)(nil), // 48: headscale.v1.GetNodeRoutesResponse
(*ExpireApiKeyResponse)(nil), // 49: headscale.v1.ExpireApiKeyResponse (*DeleteRouteResponse)(nil), // 49: headscale.v1.DeleteRouteResponse
(*ListApiKeysResponse)(nil), // 50: headscale.v1.ListApiKeysResponse (*CreateApiKeyResponse)(nil), // 50: headscale.v1.CreateApiKeyResponse
(*DeleteApiKeyResponse)(nil), // 51: headscale.v1.DeleteApiKeyResponse (*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{ var file_headscale_v1_headscale_proto_depIdxs = []int32{
0, // 0: headscale.v1.HeadscaleService.GetUser:input_type -> headscale.v1.GetUserRequest 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 14, // 14: headscale.v1.HeadscaleService.RenameNode:input_type -> headscale.v1.RenameNodeRequest
15, // 15: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest 15, // 15: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest
16, // 16: headscale.v1.HeadscaleService.MoveNode:input_type -> headscale.v1.MoveNodeRequest 16, // 16: headscale.v1.HeadscaleService.MoveNode:input_type -> headscale.v1.MoveNodeRequest
17, // 17: headscale.v1.HeadscaleService.GetRoutes:input_type -> headscale.v1.GetRoutesRequest 17, // 17: headscale.v1.HeadscaleService.BackfillNodeIPs:input_type -> headscale.v1.BackfillNodeIPsRequest
18, // 18: headscale.v1.HeadscaleService.EnableRoute:input_type -> headscale.v1.EnableRouteRequest 18, // 18: headscale.v1.HeadscaleService.GetRoutes:input_type -> headscale.v1.GetRoutesRequest
19, // 19: headscale.v1.HeadscaleService.DisableRoute:input_type -> headscale.v1.DisableRouteRequest 19, // 19: headscale.v1.HeadscaleService.EnableRoute:input_type -> headscale.v1.EnableRouteRequest
20, // 20: headscale.v1.HeadscaleService.GetNodeRoutes:input_type -> headscale.v1.GetNodeRoutesRequest 20, // 20: headscale.v1.HeadscaleService.DisableRoute:input_type -> headscale.v1.DisableRouteRequest
21, // 21: headscale.v1.HeadscaleService.DeleteRoute:input_type -> headscale.v1.DeleteRouteRequest 21, // 21: headscale.v1.HeadscaleService.GetNodeRoutes:input_type -> headscale.v1.GetNodeRoutesRequest
22, // 22: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest 22, // 22: headscale.v1.HeadscaleService.DeleteRoute:input_type -> headscale.v1.DeleteRouteRequest
23, // 23: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest 23, // 23: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest
24, // 24: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest 24, // 24: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest
25, // 25: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest 25, // 25: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest
26, // 26: headscale.v1.HeadscaleService.GetUser:output_type -> headscale.v1.GetUserResponse 26, // 26: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest
27, // 27: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse 27, // 27: headscale.v1.HeadscaleService.GetUser:output_type -> headscale.v1.GetUserResponse
28, // 28: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse 28, // 28: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse
29, // 29: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse 29, // 29: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse
30, // 30: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse 30, // 30: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse
31, // 31: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse 31, // 31: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse
32, // 32: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse 32, // 32: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse
33, // 33: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse 33, // 33: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse
34, // 34: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse 34, // 34: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse
35, // 35: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse 35, // 35: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse
36, // 36: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse 36, // 36: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse
37, // 37: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse 37, // 37: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse
38, // 38: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse 38, // 38: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse
39, // 39: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse 39, // 39: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse
40, // 40: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse 40, // 40: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse
41, // 41: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse 41, // 41: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse
42, // 42: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse 42, // 42: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse
43, // 43: headscale.v1.HeadscaleService.GetRoutes:output_type -> headscale.v1.GetRoutesResponse 43, // 43: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse
44, // 44: headscale.v1.HeadscaleService.EnableRoute:output_type -> headscale.v1.EnableRouteResponse 44, // 44: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse
45, // 45: headscale.v1.HeadscaleService.DisableRoute:output_type -> headscale.v1.DisableRouteResponse 45, // 45: headscale.v1.HeadscaleService.GetRoutes:output_type -> headscale.v1.GetRoutesResponse
46, // 46: headscale.v1.HeadscaleService.GetNodeRoutes:output_type -> headscale.v1.GetNodeRoutesResponse 46, // 46: headscale.v1.HeadscaleService.EnableRoute:output_type -> headscale.v1.EnableRouteResponse
47, // 47: headscale.v1.HeadscaleService.DeleteRoute:output_type -> headscale.v1.DeleteRouteResponse 47, // 47: headscale.v1.HeadscaleService.DisableRoute:output_type -> headscale.v1.DisableRouteResponse
48, // 48: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse 48, // 48: headscale.v1.HeadscaleService.GetNodeRoutes:output_type -> headscale.v1.GetNodeRoutesResponse
49, // 49: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse 49, // 49: headscale.v1.HeadscaleService.DeleteRoute:output_type -> headscale.v1.DeleteRouteResponse
50, // 50: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse 50, // 50: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse
51, // 51: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse 51, // 51: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse
26, // [26:52] is the sub-list for method output_type 52, // 52: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse
0, // [0:26] is the sub-list for method input_type 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 type_name
0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name 0, // [0:0] is the sub-list for field type_name

View file

@ -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) { 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 protoReq GetRoutesRequest
var metadata runtime.ServerMetadata 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) { mux.Handle("GET", pattern_HeadscaleService_GetRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() 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) { mux.Handle("GET", pattern_HeadscaleService_GetRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() 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_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_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"}, "")) 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_MoveNode_0 = runtime.ForwardResponseMessage
forward_HeadscaleService_BackfillNodeIPs_0 = runtime.ForwardResponseMessage
forward_HeadscaleService_GetRoutes_0 = runtime.ForwardResponseMessage forward_HeadscaleService_GetRoutes_0 = runtime.ForwardResponseMessage
forward_HeadscaleService_EnableRoute_0 = runtime.ForwardResponseMessage forward_HeadscaleService_EnableRoute_0 = runtime.ForwardResponseMessage

View file

@ -36,6 +36,7 @@ const (
HeadscaleService_RenameNode_FullMethodName = "/headscale.v1.HeadscaleService/RenameNode" HeadscaleService_RenameNode_FullMethodName = "/headscale.v1.HeadscaleService/RenameNode"
HeadscaleService_ListNodes_FullMethodName = "/headscale.v1.HeadscaleService/ListNodes" HeadscaleService_ListNodes_FullMethodName = "/headscale.v1.HeadscaleService/ListNodes"
HeadscaleService_MoveNode_FullMethodName = "/headscale.v1.HeadscaleService/MoveNode" HeadscaleService_MoveNode_FullMethodName = "/headscale.v1.HeadscaleService/MoveNode"
HeadscaleService_BackfillNodeIPs_FullMethodName = "/headscale.v1.HeadscaleService/BackfillNodeIPs"
HeadscaleService_GetRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetRoutes" HeadscaleService_GetRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetRoutes"
HeadscaleService_EnableRoute_FullMethodName = "/headscale.v1.HeadscaleService/EnableRoute" HeadscaleService_EnableRoute_FullMethodName = "/headscale.v1.HeadscaleService/EnableRoute"
HeadscaleService_DisableRoute_FullMethodName = "/headscale.v1.HeadscaleService/DisableRoute" 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) RenameNode(ctx context.Context, in *RenameNodeRequest, opts ...grpc.CallOption) (*RenameNodeResponse, error)
ListNodes(ctx context.Context, in *ListNodesRequest, opts ...grpc.CallOption) (*ListNodesResponse, error) ListNodes(ctx context.Context, in *ListNodesRequest, opts ...grpc.CallOption) (*ListNodesResponse, error)
MoveNode(ctx context.Context, in *MoveNodeRequest, opts ...grpc.CallOption) (*MoveNodeResponse, 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 --- // --- Route start ---
GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error) GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error)
EnableRoute(ctx context.Context, in *EnableRouteRequest, opts ...grpc.CallOption) (*EnableRouteResponse, 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 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) { func (c *headscaleServiceClient) GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error) {
out := new(GetRoutesResponse) out := new(GetRoutesResponse)
err := c.cc.Invoke(ctx, HeadscaleService_GetRoutes_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, HeadscaleService_GetRoutes_FullMethodName, in, out, opts...)
@ -350,6 +361,7 @@ type HeadscaleServiceServer interface {
RenameNode(context.Context, *RenameNodeRequest) (*RenameNodeResponse, error) RenameNode(context.Context, *RenameNodeRequest) (*RenameNodeResponse, error)
ListNodes(context.Context, *ListNodesRequest) (*ListNodesResponse, error) ListNodes(context.Context, *ListNodesRequest) (*ListNodesResponse, error)
MoveNode(context.Context, *MoveNodeRequest) (*MoveNodeResponse, error) MoveNode(context.Context, *MoveNodeRequest) (*MoveNodeResponse, error)
BackfillNodeIPs(context.Context, *BackfillNodeIPsRequest) (*BackfillNodeIPsResponse, error)
// --- Route start --- // --- Route start ---
GetRoutes(context.Context, *GetRoutesRequest) (*GetRoutesResponse, error) GetRoutes(context.Context, *GetRoutesRequest) (*GetRoutesResponse, error)
EnableRoute(context.Context, *EnableRouteRequest) (*EnableRouteResponse, 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) { func (UnimplementedHeadscaleServiceServer) MoveNode(context.Context, *MoveNodeRequest) (*MoveNodeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MoveNode not implemented") 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) { func (UnimplementedHeadscaleServiceServer) GetRoutes(context.Context, *GetRoutesRequest) (*GetRoutesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoutes not implemented") 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) 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) { func _HeadscaleService_GetRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetRoutesRequest) in := new(GetRoutesRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -1002,6 +1035,10 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{
MethodName: "MoveNode", MethodName: "MoveNode",
Handler: _HeadscaleService_MoveNode_Handler, Handler: _HeadscaleService_MoveNode_Handler,
}, },
{
MethodName: "BackfillNodeIPs",
Handler: _HeadscaleService_BackfillNodeIPs_Handler,
},
{ {
MethodName: "GetRoutes", MethodName: "GetRoutes",
Handler: _HeadscaleService_GetRoutes_Handler, Handler: _HeadscaleService_GetRoutes_Handler,

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/node.proto // source: headscale/v1/node.proto
@ -1141,6 +1141,100 @@ func (x *DebugCreateNodeResponse) GetNode() *Node {
return nil 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 protoreflect.FileDescriptor
var file_headscale_v1_node_proto_rawDesc = []byte{ 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, 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, 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, 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, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x36, 0x0a, 0x16, 0x42, 0x61, 0x63, 0x6b,
0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18,
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x22, 0x33, 0x0a, 0x17, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65,
0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x49, 0x50, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63,
0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68,
0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x03, 0x42, 0x29, 0x5a, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49,
0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50,
0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47,
0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 ( 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_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{}{ var file_headscale_v1_node_proto_goTypes = []interface{}{
(RegisterMethod)(0), // 0: headscale.v1.RegisterMethod (RegisterMethod)(0), // 0: headscale.v1.RegisterMethod
(*Node)(nil), // 1: headscale.v1.Node (*Node)(nil), // 1: headscale.v1.Node
@ -1309,16 +1410,18 @@ var file_headscale_v1_node_proto_goTypes = []interface{}{
(*MoveNodeResponse)(nil), // 17: headscale.v1.MoveNodeResponse (*MoveNodeResponse)(nil), // 17: headscale.v1.MoveNodeResponse
(*DebugCreateNodeRequest)(nil), // 18: headscale.v1.DebugCreateNodeRequest (*DebugCreateNodeRequest)(nil), // 18: headscale.v1.DebugCreateNodeRequest
(*DebugCreateNodeResponse)(nil), // 19: headscale.v1.DebugCreateNodeResponse (*DebugCreateNodeResponse)(nil), // 19: headscale.v1.DebugCreateNodeResponse
(*User)(nil), // 20: headscale.v1.User (*BackfillNodeIPsRequest)(nil), // 20: headscale.v1.BackfillNodeIPsRequest
(*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp (*BackfillNodeIPsResponse)(nil), // 21: headscale.v1.BackfillNodeIPsResponse
(*PreAuthKey)(nil), // 22: headscale.v1.PreAuthKey (*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{ var file_headscale_v1_node_proto_depIdxs = []int32{
20, // 0: headscale.v1.Node.user:type_name -> headscale.v1.User 22, // 0: headscale.v1.Node.user:type_name -> headscale.v1.User
21, // 1: headscale.v1.Node.last_seen:type_name -> google.protobuf.Timestamp 23, // 1: headscale.v1.Node.last_seen:type_name -> google.protobuf.Timestamp
21, // 2: headscale.v1.Node.expiry:type_name -> google.protobuf.Timestamp 23, // 2: headscale.v1.Node.expiry:type_name -> google.protobuf.Timestamp
22, // 3: headscale.v1.Node.pre_auth_key:type_name -> headscale.v1.PreAuthKey 24, // 3: headscale.v1.Node.pre_auth_key:type_name -> headscale.v1.PreAuthKey
21, // 4: headscale.v1.Node.created_at:type_name -> google.protobuf.Timestamp 23, // 4: headscale.v1.Node.created_at:type_name -> google.protobuf.Timestamp
0, // 5: headscale.v1.Node.register_method:type_name -> headscale.v1.RegisterMethod 0, // 5: headscale.v1.Node.register_method:type_name -> headscale.v1.RegisterMethod
1, // 6: headscale.v1.RegisterNodeResponse.node:type_name -> headscale.v1.Node 1, // 6: headscale.v1.RegisterNodeResponse.node:type_name -> headscale.v1.Node
1, // 7: headscale.v1.GetNodeResponse.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 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{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
@ -1578,7 +1705,7 @@ func file_headscale_v1_node_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_headscale_v1_node_proto_rawDesc, RawDescriptor: file_headscale_v1_node_proto_rawDesc,
NumEnums: 1, NumEnums: 1,
NumMessages: 19, NumMessages: 21,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/preauthkey.proto // source: headscale/v1/preauthkey.proto

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/routes.proto // source: headscale/v1/routes.proto

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc (unknown) // protoc (unknown)
// source: headscale/v1/user.proto // source: headscale/v1/user.proto

View file

@ -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": { "/api/v1/node/register": {
"post": { "post": {
"operationId": "HeadscaleService_RegisterNode", "operationId": "HeadscaleService_RegisterNode",
@ -886,6 +916,17 @@
} }
} }
}, },
"v1BackfillNodeIPsResponse": {
"type": "object",
"properties": {
"changes": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1CreateApiKeyRequest": { "v1CreateApiKeyRequest": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -9,7 +9,6 @@ import (
"net" "net"
"net/http" "net/http"
_ "net/http/pprof" //nolint _ "net/http/pprof" //nolint
"net/netip"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@ -56,6 +55,7 @@ import (
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/util/dnsname"
) )
var ( var (
@ -148,7 +148,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
return nil, err 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 { if err != nil {
return nil, err 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 if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
// TODO(kradalby): revisit why this takes a list. // 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 // we might have routes already from Split DNS
if app.cfg.DNSConfig.Routes == nil { if app.cfg.DNSConfig.Routes == nil {
app.cfg.DNSConfig.Routes = make(map[string][]*dnstype.Resolver) app.cfg.DNSConfig.Routes = make(map[string][]*dnstype.Resolver)

View file

@ -383,7 +383,7 @@ func (h *Headscale) handleAuthKey(
ForcedTags: pak.Proto().GetAclTags(), ForcedTags: pak.Proto().GetAclTags(),
} }
addrs, err := h.ipAlloc.Next() ipv4, ipv6, err := h.ipAlloc.Next()
if err != nil { if err != nil {
log.Error(). log.Error().
Caller(). Caller().
@ -397,7 +397,7 @@ func (h *Headscale) handleAuthKey(
node, err = h.db.RegisterNode( node, err = h.db.RegisterNode(
nodeToRegister, nodeToRegister,
addrs, ipv4, ipv6,
) )
if err != nil { if err != nil {
log.Error(). log.Error().
@ -461,7 +461,6 @@ func (h *Headscale) handleAuthKey(
log.Info(). log.Info().
Str("node", registerRequest.Hostinfo.Hostname). Str("node", registerRequest.Hostinfo.Hostname).
Str("ips", strings.Join(node.IPAddresses.StringSlice(), ", ")).
Msg("Successfully authenticated via AuthKey") Msg("Successfully authenticated via AuthKey")
} }

View file

@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"net/netip"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -330,6 +331,66 @@ func NewHeadscaleDatabase(
return nil 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
},
},
}, },
) )

View file

@ -1,13 +1,17 @@
package db package db
import ( import (
"crypto/rand"
"database/sql"
"errors" "errors"
"fmt" "fmt"
"math/big"
"net/netip" "net/netip"
"sync" "sync"
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log"
"go4.org/netipx" "go4.org/netipx"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -20,13 +24,16 @@ import (
type IPAllocator struct { type IPAllocator struct {
mu sync.Mutex mu sync.Mutex
prefix4 netip.Prefix prefix4 *netip.Prefix
prefix6 netip.Prefix prefix6 *netip.Prefix
// Previous IPs handed out // Previous IPs handed out
prev4 netip.Addr prev4 netip.Addr
prev6 netip.Addr prev6 netip.Addr
// strategy used for handing out IP addresses.
strategy types.IPAllocationStrategy
// Set of all IPs handed out. // Set of all IPs handed out.
// This might not be in sync with the database, // This might not be in sync with the database,
// but it is more conservative. If saves to the // 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 // provided IPv4 and IPv6 prefix. It needs to be created
// when headscale starts and needs to finish its read // when headscale starts and needs to finish its read
// transaction before any writes to the database occur. // transaction before any writes to the database occur.
func NewIPAllocator(db *HSDatabase, prefix4, prefix6 netip.Prefix) (*IPAllocator, error) { func NewIPAllocator(
var addressesSlices []string 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 { if db != nil {
db.Read(func(rx *gorm.DB) error { err := db.Read(func(rx *gorm.DB) error {
return rx.Model(&types.Node{}).Pluck("ip_addresses", &addressesSlices).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 var ips netipx.IPSetBuilder
// Add network and broadcast addrs to used pool so they // Add network and broadcast addrs to used pool so they
// are not handed out to nodes. // are not handed out to nodes.
network4, broadcast4 := util.GetIPPrefixEndpoints(prefix4) if prefix4 != nil {
network6, broadcast6 := util.GetIPPrefixEndpoints(prefix6) network4, broadcast4 := util.GetIPPrefixEndpoints(*prefix4)
ips.Add(network4) ips.Add(network4)
ips.Add(broadcast4) ips.Add(broadcast4)
ips.Add(network6)
ips.Add(broadcast6) // 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 // Fetch all the IP Addresses currently handed out from the Database
// and add them to the used IP set. // and add them to the used IP set.
for _, slice := range addressesSlices { for _, addrStr := range append(v4s, v6s...) {
var machineAddresses types.NodeAddresses if addrStr.Valid {
err := machineAddresses.Scan(slice) addr, err := netip.ParseAddr(addrStr.String)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf("parsing IP address from database: %w", err)
"parsing IPs from database %v: %w", machineAddresses, }
err,
)
}
for _, ip := range machineAddresses { ips.Add(addr)
ips.Add(ip)
} }
} }
@ -86,42 +124,61 @@ func NewIPAllocator(db *HSDatabase, prefix4, prefix6 netip.Prefix) (*IPAllocator
) )
} }
return &IPAllocator{ ret.usedIPs = ips
usedIPs: ips,
prefix4: prefix4, return &ret, nil
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
} }
func (i *IPAllocator) Next() (types.NodeAddresses, error) { func (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) {
i.mu.Lock() i.mu.Lock()
defer i.mu.Unlock() defer i.mu.Unlock()
v4, err := i.next(i.prev4, i.prefix4) var err error
if err != nil { var ret4 *netip.Addr
return nil, fmt.Errorf("allocating IPv4 address: %w", err) 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 i.prefix6 != nil {
if err != nil { ret6, err = i.next(i.prev6, i.prefix6)
return nil, fmt.Errorf("allocating IPv6 address: %w", err) 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") var ErrCouldNotAllocateIP = errors.New("failed to allocate IP")
func (i *IPAllocator) next(prev netip.Addr, prefix netip.Prefix) (*netip.Addr, error) { func (i *IPAllocator) nextLocked(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) {
// Get the first IP in our prefix i.mu.Lock()
ip := prev.Next() 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. // TODO(kradalby): maybe this can be done less often.
set, err := i.usedIPs.IPSet() 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. // Check if the IP has already been allocated.
if set.Contains(ip) { 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 continue
} }
@ -146,3 +211,120 @@ func (i *IPAllocator) next(prev netip.Addr, prefix netip.Prefix) (*netip.Addr, e
return &ip, nil 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
}

View file

@ -1,49 +1,41 @@
package db package db
import ( import (
"database/sql"
"fmt"
"net/netip" "net/netip"
"os" "strings"
"testing" "testing"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
) )
func TestIPAllocator(t *testing.T) { var mpp = func(pref string) *netip.Prefix {
mpp := func(pref string) netip.Prefix { p := netip.MustParsePrefix(pref)
return netip.MustParsePrefix(pref) return &p
} }
na := func(pref string) netip.Addr { var na = func(pref string) netip.Addr {
return netip.MustParseAddr(pref) return netip.MustParseAddr(pref)
} }
newDb := func() *HSDatabase { var nap = func(pref string) *netip.Addr {
tmpDir, err := os.MkdirTemp("", "headscale-db-test-*") n := na(pref)
if err != nil { return &n
t.Fatalf("creating temp dir: %s", err) }
}
db, _ = NewHeadscaleDatabase(
types.DatabaseConfig{
Type: "sqlite3",
Sqlite: types.SqliteConfig{
Path: tmpDir + "/headscale_test.db",
},
},
"",
)
return db
}
func TestIPAllocatorSequential(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
dbFunc func() *HSDatabase dbFunc func() *HSDatabase
prefix4 netip.Prefix prefix4 *netip.Prefix
prefix6 netip.Prefix prefix6 *netip.Prefix
getCount int getCount int
want []types.NodeAddresses want4 []netip.Addr
want6 []netip.Addr
}{ }{
{ {
name: "simple", name: "simple",
@ -56,23 +48,49 @@ func TestIPAllocator(t *testing.T) {
getCount: 1, getCount: 1,
want: []types.NodeAddresses{ want4: []netip.Addr{
{ na("100.64.0.1"),
na("100.64.0.1"), },
na("fd7a:115c:a1e0::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", name: "simple-with-db",
dbFunc: func() *HSDatabase { dbFunc: func() *HSDatabase {
db := newDb() db := dbForTest(t, "simple-with-db")
db.DB.Save(&types.Node{ db.DB.Save(&types.Node{
IPAddresses: types.NodeAddresses{ IPv4: nap("100.64.0.1"),
na("100.64.0.1"), IPv6: nap("fd7a:115c:a1e0::1"),
na("fd7a:115c:a1e0::1"),
},
}) })
return db return db
@ -83,23 +101,21 @@ func TestIPAllocator(t *testing.T) {
getCount: 1, getCount: 1,
want: []types.NodeAddresses{ want4: []netip.Addr{
{ na("100.64.0.2"),
na("100.64.0.2"), },
na("fd7a:115c:a1e0::2"), want6: []netip.Addr{
}, na("fd7a:115c:a1e0::2"),
}, },
}, },
{ {
name: "before-after-free-middle-in-db", name: "before-after-free-middle-in-db",
dbFunc: func() *HSDatabase { dbFunc: func() *HSDatabase {
db := newDb() db := dbForTest(t, "before-after-free-middle-in-db")
db.DB.Save(&types.Node{ db.DB.Save(&types.Node{
IPAddresses: types.NodeAddresses{ IPv4: nap("100.64.0.2"),
na("100.64.0.2"), IPv6: nap("fd7a:115c:a1e0::2"),
na("fd7a:115c:a1e0::2"),
},
}) })
return db return db
@ -110,15 +126,13 @@ func TestIPAllocator(t *testing.T) {
getCount: 2, getCount: 2,
want: []types.NodeAddresses{ want4: []netip.Addr{
{ na("100.64.0.1"),
na("100.64.0.1"), na("100.64.0.3"),
na("fd7a:115c:a1e0::1"), },
}, want6: []netip.Addr{
{ na("fd7a:115c:a1e0::1"),
na("100.64.0.3"), na("fd7a:115c:a1e0::3"),
na("fd7a:115c:a1e0::3"),
},
}, },
}, },
} }
@ -127,24 +141,347 @@ func TestIPAllocator(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
db := tt.dbFunc() db := tt.dbFunc()
alloc, _ := NewIPAllocator(db, tt.prefix4, tt.prefix6) alloc, _ := NewIPAllocator(
db,
tt.prefix4,
tt.prefix6,
types.IPAllocationStrategySequential,
)
spew.Dump(alloc) spew.Dump(alloc)
t.Logf("prefixes: %q, %q", tt.prefix4.String(), tt.prefix6.String()) var got4s []netip.Addr
var got6s []netip.Addr
var got []types.NodeAddresses
for range tt.getCount { for range tt.getCount {
gotSet, err := alloc.Next() got4, got6, err := alloc.Next()
if err != nil { if err != nil {
t.Fatalf("allocating next IP: %s", err) 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 != "" { if diff := cmp.Diff(tt.want4, got4s, util.Comparers...); diff != "" {
t.Errorf("IPAllocator unexpected result (-want +got):\n%s", 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)
} }
}) })
} }

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"sort" "sort"
"strings"
"time" "time"
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
@ -294,7 +293,8 @@ func RegisterNodeFromAuthCallback(
userName string, userName string,
nodeExpiry *time.Time, nodeExpiry *time.Time,
registrationMethod string, registrationMethod string,
addrs types.NodeAddresses, ipv4 *netip.Addr,
ipv6 *netip.Addr,
) (*types.Node, error) { ) (*types.Node, error) {
log.Debug(). log.Debug().
Str("machine_key", mkey.ShortString()). Str("machine_key", mkey.ShortString()).
@ -330,7 +330,7 @@ func RegisterNodeFromAuthCallback(
node, err := RegisterNode( node, err := RegisterNode(
tx, tx,
registrationNode, registrationNode,
addrs, ipv4, ipv6,
) )
if err == nil { if err == nil {
@ -346,14 +346,14 @@ func RegisterNodeFromAuthCallback(
return nil, ErrNodeNotFoundRegistrationCache 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 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. // 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(). log.Debug().
Str("node", node.Hostname). Str("node", node.Hostname).
Str("machine_key", node.MachineKey.ShortString()). 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). Str("user", node.User.Name).
Msg("Registering node") 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 // so we store the node.Expire and node.Nodekey that has been set when
// adding it to the registrationCache // 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 { if err := tx.Save(&node).Error; err != nil {
return nil, fmt.Errorf("failed register existing node in the database: %w", err) 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 return &node, nil
} }
node.IPAddresses = addrs node.IPv4 = ipv4
node.IPv6 = ipv6
if err := tx.Save(&node).Error; err != nil { if err := tx.Save(&node).Error; err != nil {
return nil, fmt.Errorf("failed register(save) node in the database: %w", err) 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(). log.Trace().
Caller(). Caller().
Str("node", node.Hostname). Str("node", node.Hostname).
Str("ip", strings.Join(addrs.StringSlice(), ",")).
Msg("Node registered with the database") Msg("Node registered with the database")
return &node, nil return &node, nil

View file

@ -188,13 +188,12 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
nodeKey := key.NewNode() nodeKey := key.NewNode()
machineKey := key.NewMachine() machineKey := key.NewMachine()
v4 := netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1)))
node := types.Node{ node := types.Node{
ID: types.NodeID(index), ID: types.NodeID(index),
MachineKey: machineKey.Public(), MachineKey: machineKey.Public(),
NodeKey: nodeKey.Public(), NodeKey: nodeKey.Public(),
IPAddresses: types.NodeAddresses{ IPv4: &v4,
netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))),
},
Hostname: "testnode" + strconv.Itoa(index), Hostname: "testnode" + strconv.Itoa(index),
UserID: stor[index%2].user.ID, UserID: stor[index%2].user.ID,
RegisterMethod: util.RegisterMethodAuthKey, RegisterMethod: util.RegisterMethodAuthKey,
@ -301,27 +300,6 @@ func (s *Suite) TestExpireNode(c *check.C) {
c.Assert(nodeFromDB.IsExpired(), check.Equals, true) 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) { func (s *Suite) TestGenerateGivenName(c *check.C) {
user1, err := db.CreateUser("user-1") user1, err := db.CreateUser("user-1")
c.Assert(err, check.IsNil) 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 // Check if a subprefix of an autoapproved route is approved
route2 := netip.MustParsePrefix("10.11.0.0/24") route2 := netip.MustParsePrefix("10.11.0.0/24")
v4 := netip.MustParseAddr("100.64.0.1")
node := types.Node{ node := types.Node{
ID: 0, ID: 0,
MachineKey: machineKey.Public(), MachineKey: machineKey.Public(),
@ -573,7 +552,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
RequestTags: []string{"tag:exit"}, RequestTags: []string{"tag:exit"},
RoutableIPs: []netip.Prefix{defaultRouteV4, defaultRouteV6, route1, route2}, RoutableIPs: []netip.Prefix{defaultRouteV4, defaultRouteV6, route1, route2},
}, },
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, IPv4: &v4,
} }
db.DB.Save(&node) db.DB.Save(&node)

View file

@ -609,7 +609,7 @@ func EnableAutoApprovedRoutes(
aclPolicy *policy.ACLPolicy, aclPolicy *policy.ACLPolicy,
node *types.Node, node *types.Node,
) error { ) 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 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 // 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) approvedRoutes = append(approvedRoutes, advertisedRoute)
} }
} }

View file

@ -3,6 +3,7 @@ package hscontrol
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
@ -195,7 +196,7 @@ func (api headscaleV1APIServer) RegisterNode(
return nil, err return nil, err
} }
addrs, err := api.h.ipAlloc.Next() ipv4, ipv6, err := api.h.ipAlloc.Next()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -208,7 +209,7 @@ func (api headscaleV1APIServer) RegisterNode(
request.GetUser(), request.GetUser(),
nil, nil,
util.RegisterMethodCLI, util.RegisterMethodCLI,
addrs, ipv4, ipv6,
) )
}) })
if err != nil { if err != nil {
@ -468,6 +469,24 @@ func (api headscaleV1APIServer) MoveNode(
return &v1.MoveNodeResponse{Node: node.Proto()}, nil 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( func (api headscaleV1APIServer) GetRoutes(
ctx context.Context, ctx context.Context,
request *v1.GetRoutesRequest, request *v1.GetRoutesRequest,

View file

@ -174,8 +174,8 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node) {
"device_model": []string{node.Hostinfo.OS}, "device_model": []string{node.Hostinfo.OS},
} }
if len(node.IPAddresses) > 0 { if len(node.IPs()) > 0 {
attrs.Add("device_ip", node.IPAddresses[0].String()) attrs.Add("device_ip", node.IPs()[0].String())
} }
resolver.Addr = fmt.Sprintf("%s?%s", resolver.Addr, attrs.Encode()) resolver.Addr = fmt.Sprintf("%s?%s", resolver.Addr, attrs.Encode())

View file

@ -17,6 +17,11 @@ import (
"tailscale.com/types/key" "tailscale.com/types/key"
) )
var iap = func(ipStr string) *netip.Addr {
ip := netip.MustParseAddr(ipStr)
return &ip
}
func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
mach := func(hostname, username string, userid uint) *types.Node { mach := func(hostname, username string, userid uint) *types.Node {
return &types.Node{ return &types.Node{
@ -176,17 +181,17 @@ func Test_fullMapResponse(t *testing.T) {
DiscoKey: mustDK( DiscoKey: mustDK(
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
), ),
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, IPv4: iap("100.64.0.1"),
Hostname: "mini", Hostname: "mini",
GivenName: "mini", GivenName: "mini",
UserID: 0, UserID: 0,
User: types.User{Name: "mini"}, User: types.User{Name: "mini"},
ForcedTags: []string{}, ForcedTags: []string{},
AuthKeyID: 0, AuthKeyID: 0,
AuthKey: &types.PreAuthKey{}, AuthKey: &types.PreAuthKey{},
LastSeen: &lastSeen, LastSeen: &lastSeen,
Expiry: &expire, Expiry: &expire,
Hostinfo: &tailcfg.Hostinfo{}, Hostinfo: &tailcfg.Hostinfo{},
Routes: []types.Route{ Routes: []types.Route{
{ {
Prefix: types.IPPrefix(netip.MustParsePrefix("0.0.0.0/0")), Prefix: types.IPPrefix(netip.MustParsePrefix("0.0.0.0/0")),
@ -257,17 +262,17 @@ func Test_fullMapResponse(t *testing.T) {
DiscoKey: mustDK( DiscoKey: mustDK(
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
), ),
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, IPv4: iap("100.64.0.2"),
Hostname: "peer1", Hostname: "peer1",
GivenName: "peer1", GivenName: "peer1",
UserID: 0, UserID: 0,
User: types.User{Name: "mini"}, User: types.User{Name: "mini"},
ForcedTags: []string{}, ForcedTags: []string{},
LastSeen: &lastSeen, LastSeen: &lastSeen,
Expiry: &expire, Expiry: &expire,
Hostinfo: &tailcfg.Hostinfo{}, Hostinfo: &tailcfg.Hostinfo{},
Routes: []types.Route{}, Routes: []types.Route{},
CreatedAt: created, CreatedAt: created,
} }
tailPeer1 := &tailcfg.Node{ tailPeer1 := &tailcfg.Node{
@ -312,17 +317,17 @@ func Test_fullMapResponse(t *testing.T) {
DiscoKey: mustDK( DiscoKey: mustDK(
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
), ),
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, IPv4: iap("100.64.0.3"),
Hostname: "peer2", Hostname: "peer2",
GivenName: "peer2", GivenName: "peer2",
UserID: 1, UserID: 1,
User: types.User{Name: "peer2"}, User: types.User{Name: "peer2"},
ForcedTags: []string{}, ForcedTags: []string{},
LastSeen: &lastSeen, LastSeen: &lastSeen,
Expiry: &expire, Expiry: &expire,
Hostinfo: &tailcfg.Hostinfo{}, Hostinfo: &tailcfg.Hostinfo{},
Routes: []types.Route{}, Routes: []types.Route{},
CreatedAt: created, CreatedAt: created,
} }
tests := []struct { tests := []struct {

View file

@ -44,7 +44,7 @@ func tailNode(
pol *policy.ACLPolicy, pol *policy.ACLPolicy,
cfg *types.Config, cfg *types.Config,
) (*tailcfg.Node, error) { ) (*tailcfg.Node, error) {
addrs := node.IPAddresses.Prefixes() addrs := node.Prefixes()
allowedIPs := append( allowedIPs := append(
[]netip.Prefix{}, []netip.Prefix{},

View file

@ -89,9 +89,7 @@ func TestTailNode(t *testing.T) {
DiscoKey: mustDK( DiscoKey: mustDK(
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
), ),
IPAddresses: []netip.Addr{ IPv4: iap("100.64.0.1"),
netip.MustParseAddr("100.64.0.1"),
},
Hostname: "mini", Hostname: "mini",
GivenName: "mini", GivenName: "mini",
UserID: 0, UserID: 0,

View file

@ -597,7 +597,7 @@ func (h *Headscale) registerNodeForOIDCCallback(
machineKey *key.MachinePublic, machineKey *key.MachinePublic,
expiry time.Time, expiry time.Time,
) error { ) error {
addrs, err := h.ipAlloc.Next() ipv4, ipv6, err := h.ipAlloc.Next()
if err != nil { if err != nil {
return err return err
} }
@ -611,7 +611,7 @@ func (h *Headscale) registerNodeForOIDCCallback(
user.Name, user.Name,
&expiry, &expiry,
util.RegisterMethodOIDC, util.RegisterMethodOIDC,
addrs, ipv4, ipv6,
); err != nil { ); err != nil {
return err return err
} }

View file

@ -229,7 +229,7 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
continue continue
} }
if node.IPAddresses.InIPSet(expanded) { if node.InIPSet(expanded) {
dests = append(dests, dest) dests = append(dests, dest)
} }
@ -306,7 +306,7 @@ func (pol *ACLPolicy) CompileSSHPolicy(
return nil, err return nil, err
} }
if !node.IPAddresses.InIPSet(destSet) { if !node.InIPSet(destSet) {
continue continue
} }
@ -744,7 +744,7 @@ func (pol *ACLPolicy) expandIPsFromGroup(
for _, user := range users { for _, user := range users {
filteredNodes := filterNodesByUser(nodes, user) filteredNodes := filterNodesByUser(nodes, user)
for _, node := range filteredNodes { for _, node := range filteredNodes {
node.IPAddresses.AppendToIPSet(&build) node.AppendToIPSet(&build)
} }
} }
@ -760,7 +760,7 @@ func (pol *ACLPolicy) expandIPsFromTag(
// check for forced tags // check for forced tags
for _, node := range nodes { for _, node := range nodes {
if util.StringOrPrefixListContains(node.ForcedTags, alias) { 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) { 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 { for _, node := range filteredNodes {
node.IPAddresses.AppendToIPSet(&build) node.AppendToIPSet(&build)
} }
return build.IPSet() return build.IPSet()
@ -833,7 +833,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP(
build.Add(ip) build.Add(ip)
for _, node := range matches { for _, node := range matches {
node.IPAddresses.AppendToIPSet(&build) node.AppendToIPSet(&build)
} }
return build.IPSet() 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 // 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. // addresses for the hosts that belong to tailscale. This doesnt really affect stuff like subnet routers.
for _, node := range nodes { for _, node := range nodes {
for _, ip := range node.IPAddresses { for _, ip := range node.IPs() {
// log.Trace(). // 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()) // 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) { if prefix.Contains(ip) {
node.IPAddresses.AppendToIPSet(&build) node.AppendToIPSet(&build)
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -31,6 +31,13 @@ var errOidcMutuallyExclusive = errors.New(
"oidc_client_secret and oidc_client_secret_path are mutually exclusive", "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. // Config contains the initial Headscale configuration.
type Config struct { type Config struct {
ServerURL string ServerURL string
@ -42,6 +49,7 @@ type Config struct {
NodeUpdateCheckInterval time.Duration NodeUpdateCheckInterval time.Duration
PrefixV4 *netip.Prefix PrefixV4 *netip.Prefix
PrefixV6 *netip.Prefix PrefixV6 *netip.Prefix
IPAllocation IPAllocationStrategy
NoisePrivateKeyPath string NoisePrivateKeyPath string
BaseDomain string BaseDomain string
Log LogConfig Log LogConfig
@ -230,6 +238,8 @@ func LoadConfig(path string, isFile bool) error {
viper.SetDefault("tuning.batch_change_delay", "800ms") viper.SetDefault("tuning.batch_change_delay", "800ms")
viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30) viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30)
viper.SetDefault("prefixes.allocation", IPAllocationStrategySequential)
if IsCLIConfigured() { if IsCLIConfigured() {
return nil return nil
} }
@ -579,18 +589,16 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
return nil, "" return nil, ""
} }
func Prefixes() (*netip.Prefix, *netip.Prefix, error) { func PrefixV4() (*netip.Prefix, error) {
prefixV4Str := viper.GetString("prefixes.v4") prefixV4Str := viper.GetString("prefixes.v4")
prefixV6Str := viper.GetString("prefixes.v6")
if prefixV4Str == "" {
return nil, nil
}
prefixV4, err := netip.ParsePrefix(prefixV4Str) prefixV4, err := netip.ParsePrefix(prefixV4Str)
if err != nil { if err != nil {
return nil, nil, err return nil, fmt.Errorf("parsing IPv4 prefix from config: %w", err)
}
prefixV6, err := netip.ParsePrefix(prefixV6Str)
if err != nil {
return nil, nil, err
} }
builder := netipx.IPSetBuilder{} builder := netipx.IPSetBuilder{}
@ -603,13 +611,33 @@ func Prefixes() (*netip.Prefix, *netip.Prefix, error) {
prefixV4Str, tsaddr.CGNATRange()) 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) { if !ipSet.ContainsPrefix(prefixV6) {
log.Warn(). log.Warn().
Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.", Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.",
prefixV6Str, tsaddr.TailscaleULARange()) prefixV6Str, tsaddr.TailscaleULARange())
} }
return &prefixV4, &prefixV6, nil return &prefixV6, nil
} }
func GetHeadscaleConfig() (*Config, error) { func GetHeadscaleConfig() (*Config, error) {
@ -624,11 +652,27 @@ func GetHeadscaleConfig() (*Config, error) {
}, nil }, nil
} }
prefix4, prefix6, err := Prefixes() prefix4, err := PrefixV4()
if err != nil { if err != nil {
return nil, err 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() dnsConfig, baseDomain := GetDNSConfig()
derpConfig := GetDERPConfig() derpConfig := GetDERPConfig()
logConfig := GetLogTailConfig() logConfig := GetLogTailConfig()
@ -655,8 +699,9 @@ func GetHeadscaleConfig() (*Config, error) {
GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"), GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"),
DisableUpdateCheck: viper.GetBool("disable_check_updates"), DisableUpdateCheck: viper.GetBool("disable_check_updates"),
PrefixV4: prefix4, PrefixV4: prefix4,
PrefixV6: prefix6, PrefixV6: prefix6,
IPAllocation: IPAllocationStrategy(alloc),
NoisePrivateKeyPath: util.AbsolutePathFromConfigPath( NoisePrivateKeyPath: util.AbsolutePathFromConfigPath(
viper.GetString("noise.private_key_path"), viper.GetString("noise.private_key_path"),

View file

@ -1,12 +1,11 @@
package types package types
import ( import (
"database/sql/driver" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/netip" "net/netip"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -14,7 +13,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/policy/matcher" "github.com/juanfont/headscale/hscontrol/policy/matcher"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log"
"go4.org/netipx" "go4.org/netipx"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm" "gorm.io/gorm"
@ -83,7 +81,19 @@ type Node struct {
HostinfoDatabaseField string `gorm:"column:host_info"` HostinfoDatabaseField string `gorm:"column:host_info"`
Hostinfo *tailcfg.Hostinfo `gorm:"-"` 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 // Hostname represents the name given by the Tailscale
// client during registration // client during registration
@ -123,89 +133,6 @@ type (
Nodes []*Node 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. // IsExpired returns whether the node registration has expired.
func (node Node) IsExpired() bool { func (node Node) IsExpired() bool {
// If Expiry is not set, the client has not indicated that // 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 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 { 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 { for _, route := range node2.Routes {
if route.Enabled { if route.Enabled {
@ -237,7 +221,7 @@ func (node *Node) CanAccess(filter []tailcfg.FilterRule, node2 *Node) bool {
// TODO(kradalby): Cache or pregen this // TODO(kradalby): Cache or pregen this
matcher := matcher.MatchFromFilterRule(rule) matcher := matcher.MatchFromFilterRule(rule)
if !matcher.SrcsContainsIPs([]netip.Addr(node.IPAddresses)) { if !matcher.SrcsContainsIPs(src) {
continue continue
} }
@ -250,13 +234,16 @@ func (node *Node) CanAccess(filter []tailcfg.FilterRule, node2 *Node) bool {
} }
func (nodes Nodes) FilterByIP(ip netip.Addr) Nodes { func (nodes Nodes) FilterByIP(ip netip.Addr) Nodes {
found := make(Nodes, 0) var found Nodes
for _, node := range nodes { for _, node := range nodes {
for _, mIP := range node.IPAddresses { if node.IPv4 != nil && ip == *node.IPv4 {
if ip == mIP { found = append(found, node)
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) hi, err := json.Marshal(node.Hostinfo)
if err != nil { 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) 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 return nil
} }
@ -296,19 +295,19 @@ func (node *Node) BeforeSave(tx *gorm.DB) error {
func (node *Node) AfterFind(tx *gorm.DB) error { func (node *Node) AfterFind(tx *gorm.DB) error {
var machineKey key.MachinePublic var machineKey key.MachinePublic
if err := machineKey.UnmarshalText([]byte(node.MachineKeyDatabaseField)); err != nil { 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 node.MachineKey = machineKey
var nodeKey key.NodePublic var nodeKey key.NodePublic
if err := nodeKey.UnmarshalText([]byte(node.NodeKeyDatabaseField)); err != nil { 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 node.NodeKey = nodeKey
var discoKey key.DiscoPublic var discoKey key.DiscoPublic
if err := discoKey.UnmarshalText([]byte(node.DiscoKeyDatabaseField)); err != nil { 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 node.DiscoKey = discoKey
@ -316,7 +315,7 @@ func (node *Node) AfterFind(tx *gorm.DB) error {
for idx, ep := range node.EndpointsDatabaseField { for idx, ep := range node.EndpointsDatabaseField {
addrPort, err := netip.ParseAddrPort(ep) addrPort, err := netip.ParseAddrPort(ep)
if err != nil { 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 endpoints[idx] = addrPort
@ -325,12 +324,28 @@ func (node *Node) AfterFind(tx *gorm.DB) error {
var hi tailcfg.Hostinfo var hi tailcfg.Hostinfo
if err := json.Unmarshal([]byte(node.HostinfoDatabaseField), &hi); err != nil { if err := json.Unmarshal([]byte(node.HostinfoDatabaseField), &hi); err != nil {
log.Trace().Err(err).Msgf("Hostinfo content: %s", node.HostinfoDatabaseField) return fmt.Errorf("unmarshalling hostinfo from database: %w", err)
return fmt.Errorf("failed to unmarshal Hostinfo from db: %w", err)
} }
node.Hostinfo = &hi 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 return nil
} }
@ -339,9 +354,11 @@ func (node *Node) Proto() *v1.Node {
Id: uint64(node.ID), Id: uint64(node.ID),
MachineKey: node.MachineKey.String(), MachineKey: node.MachineKey.String(),
NodeKey: node.NodeKey.String(), NodeKey: node.NodeKey.String(),
DiscoKey: node.DiscoKey.String(), DiscoKey: node.DiscoKey.String(),
IpAddresses: node.IPAddresses.StringSlice(),
// TODO(kradalby): replace list with v4, v6 field?
IpAddresses: node.IPsAsString(),
Name: node.Hostname, Name: node.Hostname,
GivenName: node.GivenName, GivenName: node.GivenName,
User: node.User.Proto(), User: node.User.Proto(),

View file

@ -12,6 +12,10 @@ import (
) )
func Test_NodeCanAccess(t *testing.T) { func Test_NodeCanAccess(t *testing.T) {
iap := func(ipStr string) *netip.Addr {
ip := netip.MustParseAddr(ipStr)
return &ip
}
tests := []struct { tests := []struct {
name string name string
node1 Node node1 Node
@ -22,10 +26,10 @@ func Test_NodeCanAccess(t *testing.T) {
{ {
name: "no-rules", name: "no-rules",
node1: Node{ node1: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, IPv4: iap("10.0.0.1"),
}, },
node2: Node{ node2: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, IPv4: iap("10.0.0.2"),
}, },
rules: []tailcfg.FilterRule{}, rules: []tailcfg.FilterRule{},
want: false, want: false,
@ -33,10 +37,10 @@ func Test_NodeCanAccess(t *testing.T) {
{ {
name: "wildcard", name: "wildcard",
node1: Node{ node1: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, IPv4: iap("10.0.0.1"),
}, },
node2: Node{ node2: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, IPv4: iap("10.0.0.2"),
}, },
rules: []tailcfg.FilterRule{ rules: []tailcfg.FilterRule{
{ {
@ -54,10 +58,10 @@ func Test_NodeCanAccess(t *testing.T) {
{ {
name: "other-cant-access-src", name: "other-cant-access-src",
node1: Node{ node1: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")}, IPv4: iap("100.64.0.1"),
}, },
node2: Node{ node2: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, IPv4: iap("100.64.0.3"),
}, },
rules: []tailcfg.FilterRule{ rules: []tailcfg.FilterRule{
{ {
@ -72,10 +76,10 @@ func Test_NodeCanAccess(t *testing.T) {
{ {
name: "dest-cant-access-src", name: "dest-cant-access-src",
node1: Node{ node1: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, IPv4: iap("100.64.0.3"),
}, },
node2: Node{ node2: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, IPv4: iap("100.64.0.2"),
}, },
rules: []tailcfg.FilterRule{ rules: []tailcfg.FilterRule{
{ {
@ -90,10 +94,10 @@ func Test_NodeCanAccess(t *testing.T) {
{ {
name: "src-can-access-dest", name: "src-can-access-dest",
node1: Node{ node1: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")}, IPv4: iap("100.64.0.2"),
}, },
node2: Node{ node2: Node{
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")}, IPv4: iap("100.64.0.3"),
}, },
rules: []tailcfg.FilterRule{ 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) { func TestNodeFQDN(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View file

@ -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). // 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. // 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 { func GenerateIPv4DNSRootDomain(ipPrefix 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 {
// Conversion to the std lib net.IPnet, a bit easier to operate // Conversion to the std lib net.IPnet, a bit easier to operate
netRange := netipx.PrefixIPNet(ipPrefix) netRange := netipx.PrefixIPNet(ipPrefix)
maskBits, _ := netRange.Mask.Size() maskBits, _ := netRange.Mask.Size()
@ -165,7 +139,27 @@ func generateIPv4DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
return fqdns 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 const nibbleLen = 4
maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size() maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size()

View file

@ -148,10 +148,7 @@ func TestCheckForFQDNRules(t *testing.T) {
} }
func TestMagicDNSRootDomains100(t *testing.T) { func TestMagicDNSRootDomains100(t *testing.T) {
prefixes := []netip.Prefix{ domains := GenerateIPv4DNSRootDomain(netip.MustParsePrefix("100.64.0.0/10"))
netip.MustParsePrefix("100.64.0.0/10"),
}
domains := GenerateMagicDNSRootDomains(prefixes)
found := false found := false
for _, domain := range domains { for _, domain := range domains {
@ -185,10 +182,7 @@ func TestMagicDNSRootDomains100(t *testing.T) {
} }
func TestMagicDNSRootDomains172(t *testing.T) { func TestMagicDNSRootDomains172(t *testing.T) {
prefixes := []netip.Prefix{ domains := GenerateIPv4DNSRootDomain(netip.MustParsePrefix("172.16.0.0/16"))
netip.MustParsePrefix("172.16.0.0/16"),
}
domains := GenerateMagicDNSRootDomains(prefixes)
found := false found := false
for _, domain := range domains { for _, domain := range domains {
@ -213,20 +207,14 @@ func TestMagicDNSRootDomains172(t *testing.T) {
// Happens when netmask is a multiple of 4 bits (sounds likely). // Happens when netmask is a multiple of 4 bits (sounds likely).
func TestMagicDNSRootDomainsIPv6Single(t *testing.T) { func TestMagicDNSRootDomainsIPv6Single(t *testing.T) {
prefixes := []netip.Prefix{ domains := GenerateIPv6DNSRootDomain(netip.MustParsePrefix("fd7a:115c:a1e0::/48"))
netip.MustParsePrefix("fd7a:115c:a1e0::/48"),
}
domains := GenerateMagicDNSRootDomains(prefixes)
assert.Len(t, domains, 1) 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()) 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) { func TestMagicDNSRootDomainsIPv6SingleMultiple(t *testing.T) {
prefixes := []netip.Prefix{ domains := GenerateIPv6DNSRootDomain(netip.MustParsePrefix("fd7a:115c:a1e0::/50"))
netip.MustParsePrefix("fd7a:115c:a1e0::/50"),
}
domains := GenerateMagicDNSRootDomains(prefixes)
yieldsRoot := func(dom string) bool { yieldsRoot := func(dom string) bool {
for _, candidate := range domains { for _, candidate := range domains {

View file

@ -9,6 +9,7 @@ import (
"time" "time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" 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/hsic"
"github.com/juanfont/headscale/integration/tsic" "github.com/juanfont/headscale/integration/tsic"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -39,6 +40,7 @@ func TestPingAllByIP(t *testing.T) {
hsic.WithEmbeddedDERPServerOnly(), hsic.WithEmbeddedDERPServerOnly(),
hsic.WithTLS(), hsic.WithTLS(),
hsic.WithHostnameAsServerURL(), hsic.WithHostnameAsServerURL(),
hsic.WithIPAllocationStrategy(types.IPAllocationStrategyRandom),
) )
assertNoErrHeadscaleEnv(t, err) assertNoErrHeadscaleEnv(t, err)

View file

@ -1,5 +1,7 @@
package hsic package hsic
import "github.com/juanfont/headscale/hscontrol/types"
// const ( // const (
// defaultEphemeralNodeInactivityTimeout = time.Second * 30 // defaultEphemeralNodeInactivityTimeout = time.Second * 30
// defaultNodeUpdateCheckInterval = time.Second * 10 // defaultNodeUpdateCheckInterval = time.Second * 10
@ -129,5 +131,9 @@ func DefaultConfigEnv() map[string]string {
"HEADSCALE_DERP_URLS": "https://controlplane.tailscale.com/derpmap/default", "HEADSCALE_DERP_URLS": "https://controlplane.tailscale.com/derpmap/default",
"HEADSCALE_DERP_AUTO_UPDATE_ENABLED": "false", "HEADSCALE_DERP_AUTO_UPDATE_ENABLED": "false",
"HEADSCALE_DERP_UPDATE_FREQUENCY": "1m", "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),
} }
} }

View file

@ -24,6 +24,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/dockertestutil"
"github.com/juanfont/headscale/integration/integrationutil" "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 // WithEmbeddedDERPServerOnly configures Headscale to start
// and only use the embedded DERP server. // and only use the embedded DERP server.
// It requires WithTLS and WithHostnameAsServerURL to be // It requires WithTLS and WithHostnameAsServerURL to be

View file

@ -123,6 +123,13 @@ service HeadscaleService {
post: "/api/v1/node/{node_id}/user" post: "/api/v1/node/{node_id}/user"
}; };
} }
rpc BackfillNodeIPs(BackfillNodeIPsRequest) returns (BackfillNodeIPsResponse) {
option (google.api.http) = {
post: "/api/v1/node/backfillips"
};
}
// --- Node end --- // --- Node end ---
// --- Route start --- // --- Route start ---

View file

@ -126,3 +126,11 @@ message DebugCreateNodeRequest {
message DebugCreateNodeResponse { message DebugCreateNodeResponse {
Node node = 1; Node node = 1;
} }
message BackfillNodeIPsRequest {
bool confirmed = 1;
}
message BackfillNodeIPsResponse {
repeated string changes = 1;
}