Merge branch 'configurable-mtls' of github.com:arch4ngel/headscale into configurable-mtls

This commit is contained in:
Justin Angel 2022-01-31 10:27:57 -05:00
commit af25aa75d9
43 changed files with 955 additions and 543 deletions

View file

@ -14,22 +14,38 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v14.1
with:
files: |
go.*
**/*.go
integration_test/
config-example.yaml
- name: Setup Go
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-go@v2
with:
go-version: "1.17"
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
run: |
go version
sudo apt update
sudo apt install -y make
- name: Run build
if: steps.changed-files.outputs.any_changed == 'true'
run: make build
- uses: actions/upload-artifact@v2
if: steps.changed-files.outputs.any_changed == 'true'
with:
name: headscale-linux
path: headscale

View file

@ -8,8 +8,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v14.1
with:
files: |
go.*
**/*.go
integration_test/
config-example.yaml
- name: golangci-lint
if: steps.changed-files.outputs.any_changed == 'true'
uses: golangci/golangci-lint-action@v2
with:
version: latest
@ -24,8 +37,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v14.1
with:
files: |
**/*.md
**/*.yml
**/*.yaml
**/*.ts
**/*.js
**/*.sass
**/*.css
**/*.scss
**/*.html
- name: Prettify code
if: steps.changed-files.outputs.any_changed == 'true'
uses: creyD/prettier_action@v4.0
with:
prettier_options: >-

View file

@ -3,21 +3,30 @@ name: CI
on: [pull_request]
jobs:
# The "build" workflow
integration-test:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v14.1
with:
files: |
go.*
**/*.go
integration_test/
config-example.yaml
# Setup Go
- name: Setup Go
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-go@v2
with:
go-version: "1.17"
- name: Run Integration tests
if: steps.changed-files.outputs.any_changed == 'true'
run: go test -tags integration -timeout 30m

View file

@ -3,31 +3,41 @@ name: CI
on: [push, pull_request]
jobs:
# The "build" workflow
test:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v14.1
with:
files: |
go.*
**/*.go
integration_test/
config-example.yaml
# Setup Go
- name: Setup Go
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-go@v2
with:
go-version: "1.17" # The Go version to download (if necessary) and use.
go-version: "1.17"
# Install all the dependencies
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
run: |
go version
sudo apt update
sudo apt install -y make
- name: Run tests
if: steps.changed-files.outputs.any_changed == 'true'
run: make test
- name: Run build
if: steps.changed-files.outputs.any_changed == 'true'
run: make

View file

@ -1,8 +1,7 @@
# This is an example .goreleaser.yml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
---
before:
hooks:
- go mod tidy
- go mod tidy -compat=1.17
release:
prerelease: auto
@ -33,7 +32,7 @@ builds:
goarch:
- arm
goarm:
- 7
- "7"
env:
- CC=arm-linux-gnueabihf-gcc
- CXX=arm-linux-gnueabihf-g++

View file

@ -2,6 +2,18 @@
**TBD (TBD):**
**0.13.0 (2022-xx-xx):**
**Features**:
- Add IPv6 support to the prefix assigned to namespaces
**Changes**:
- `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208)
**0.12.4 (2022-01-29):**
**Changes**:
- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)

View file

@ -1,5 +1,5 @@
# Builder image
FROM golang:1.17.6-bullseye AS build
FROM docker.io/golang:1.17.1-bullseye AS build
ENV GOPATH /go
WORKDIR /go/src/headscale

View file

@ -1,5 +1,5 @@
# Builder image
FROM golang:1.17.6-alpine AS build
FROM docker.io/golang:1.17.1-alpine AS build
ENV GOPATH /go
WORKDIR /go/src/headscale
@ -14,7 +14,7 @@ RUN strip /go/bin/headscale
RUN test -e /go/bin/headscale
# Production image
FROM alpine:latest
FROM docker.io/alpine:latest
COPY --from=build /go/bin/headscale /bin/headscale
ENV TZ UTC

View file

@ -1,5 +1,5 @@
# Builder image
FROM golang:1.17.1-bullseye AS build
FROM docker.io/golang:1.17.1-bullseye AS build
ENV GOPATH /go
WORKDIR /go/src/headscale

View file

@ -7,5 +7,5 @@ RUN apt-get update \
&& curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | apt-key add - \
&& curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \
&& apt-get update \
&& apt-get install -y tailscale=${TAILSCALE_VERSION} \
&& apt-get install -y tailscale=${TAILSCALE_VERSION} dnsutils \
&& rm -rf /var/lib/apt/lists/*

View file

@ -18,7 +18,7 @@ test:
@go test -coverprofile=coverage.out ./...
test_integration:
go test -tags integration -timeout 30m ./...
go test -tags integration -timeout 30m -count=1 ./...
test_integration_cli:
go test -tags integration -v integration_cli_test.go integration_common_test.go

View file

@ -188,7 +188,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
return nil, errInvalidNamespace
}
for _, node := range nodes {
ips = append(ips, node.IPAddress)
ips = append(ips, node.IPAddresses.ToStringSlice()...)
}
}
@ -222,7 +222,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
// FIXME: Check TagOwners allows this
for _, t := range hostinfo.RequestTags {
if alias[4:] == t {
ips = append(ips, machine.IPAddress)
ips = append(ips, machine.IPAddresses.ToStringSlice()...)
break
}
@ -241,7 +241,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
}
ips := []string{}
for _, n := range nodes {
ips = append(ips, n.IPAddress)
ips = append(ips, n.IPAddresses.ToStringSlice()...)
}
return ips, nil

View file

@ -61,9 +61,9 @@ func (s *Suite) TestPortRange(c *check.C) {
c.Assert(rules, check.NotNil)
c.Assert(rules, check.HasLen, 1)
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(5400))
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
c.Assert(rules[0].DstPorts, check.HasLen, 1)
c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(5400))
c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
}
func (s *Suite) TestPortWildcard(c *check.C) {
@ -75,11 +75,11 @@ func (s *Suite) TestPortWildcard(c *check.C) {
c.Assert(rules, check.NotNil)
c.Assert(rules, check.HasLen, 1)
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
c.Assert((rules)[0].SrcIPs, check.HasLen, 1)
c.Assert((rules)[0].SrcIPs[0], check.Equals, "*")
c.Assert(rules[0].DstPorts, check.HasLen, 1)
c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
c.Assert(rules[0].SrcIPs, check.HasLen, 1)
c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
}
func (s *Suite) TestPortNamespace(c *check.C) {
@ -91,7 +91,7 @@ func (s *Suite) TestPortNamespace(c *check.C) {
_, err = app.GetMachine("testnamespace", "testmachine")
c.Assert(err, check.NotNil)
ip, _ := app.getAvailableIP()
ips, _ := app.getAvailableIPs()
machine := Machine{
ID: 0,
MachineKey: "foo",
@ -101,7 +101,7 @@ func (s *Suite) TestPortNamespace(c *check.C) {
NamespaceID: namespace.ID,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: ip.String(),
IPAddresses: ips,
AuthKeyID: uint(pak.ID),
}
app.db.Save(&machine)
@ -116,12 +116,13 @@ func (s *Suite) TestPortNamespace(c *check.C) {
c.Assert(rules, check.NotNil)
c.Assert(rules, check.HasLen, 1)
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
c.Assert((rules)[0].SrcIPs, check.HasLen, 1)
c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
c.Assert((rules)[0].SrcIPs[0], check.Equals, ip.String())
c.Assert(rules[0].DstPorts, check.HasLen, 1)
c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
c.Assert(rules[0].SrcIPs, check.HasLen, 1)
c.Assert(rules[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
c.Assert(len(ips), check.Equals, 1)
c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String())
}
func (s *Suite) TestPortGroup(c *check.C) {
@ -133,7 +134,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
_, err = app.GetMachine("testnamespace", "testmachine")
c.Assert(err, check.NotNil)
ip, _ := app.getAvailableIP()
ips, _ := app.getAvailableIPs()
machine := Machine{
ID: 0,
MachineKey: "foo",
@ -143,7 +144,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
NamespaceID: namespace.ID,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: ip.String(),
IPAddresses: ips,
AuthKeyID: uint(pak.ID),
}
app.db.Save(&machine)
@ -156,10 +157,11 @@ func (s *Suite) TestPortGroup(c *check.C) {
c.Assert(rules, check.NotNil)
c.Assert(rules, check.HasLen, 1)
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
c.Assert((rules)[0].SrcIPs, check.HasLen, 1)
c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
c.Assert((rules)[0].SrcIPs[0], check.Equals, ip.String())
c.Assert(rules[0].DstPorts, check.HasLen, 1)
c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
c.Assert(rules[0].SrcIPs, check.HasLen, 1)
c.Assert(rules[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
c.Assert(len(ips), check.Equals, 1)
c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String())
}

15
api.go
View file

@ -497,6 +497,7 @@ func (h *Headscale) handleMachineRegistrationNew(
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
}
// TODO: check if any locks are needed around IP allocation.
func (h *Headscale) handleAuthKey(
ctx *gin.Context,
machineKey key.MachinePublic,
@ -554,14 +555,14 @@ func (h *Headscale) handleAuthKey(
log.Debug().
Str("func", "handleAuthKey").
Str("machine", machine.Name).
Msg("Authentication key was valid, proceeding to acquire an IP address")
ip, err := h.getAvailableIP()
Msg("Authentication key was valid, proceeding to acquire IP addresses")
ips, err := h.getAvailableIPs()
if err != nil {
log.Error().
Caller().
Str("func", "handleAuthKey").
Str("machine", machine.Name).
Msg("Failed to find an available IP")
Msg("Failed to find an available IP address")
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
Inc()
@ -570,12 +571,12 @@ func (h *Headscale) handleAuthKey(
log.Info().
Str("func", "handleAuthKey").
Str("machine", machine.Name).
Str("ip", ip.String()).
Msgf("Assigning %s to %s", ip, machine.Name)
Str("ips", strings.Join(ips.ToStringSlice(), ",")).
Msgf("Assigning %s to %s", strings.Join(ips.ToStringSlice(), ","), machine.Name)
machine.Expiry = &registerRequest.Expiry
machine.AuthKeyID = uint(pak.ID)
machine.IPAddress = ip.String()
machine.IPAddresses = ips
machine.NamespaceID = pak.NamespaceID
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
@ -610,6 +611,6 @@ func (h *Headscale) handleAuthKey(
log.Info().
Str("func", "handleAuthKey").
Str("machine", machine.Name).
Str("ip", machine.IPAddress).
Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
Msg("Successfully authenticated via AuthKey")
}

6
app.go
View file

@ -73,7 +73,7 @@ type Config struct {
ServerURL string
Addr string
EphemeralNodeInactivityTimeout time.Duration
IPPrefix netaddr.IPPrefix
IPPrefixes []netaddr.IPPrefix
PrivateKeyPath string
BaseDomain string
@ -204,9 +204,7 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
}
if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
magicDNSDomains := generateMagicDNSRootDomains(
app.cfg.IPPrefix,
)
magicDNSDomains := generateMagicDNSRootDomains(app.cfg.IPPrefixes)
// we might have routes already from Split DNS
if app.cfg.DNSConfig.Routes == nil {
app.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver)

View file

@ -41,7 +41,9 @@ func (s *Suite) ResetDB(c *check.C) {
c.Fatal(err)
}
cfg := Config{
IPPrefix: netaddr.MustParseIPPrefix("10.27.0.0/23"),
IPPrefixes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.27.0.0/23"),
},
}
app = Headscale{

View file

@ -4,6 +4,7 @@ import (
"time"
"gopkg.in/check.v1"
"inet.af/netaddr"
)
func (s *Suite) TestRegisterMachine(c *check.C) {
@ -19,16 +20,17 @@ func (s *Suite) TestRegisterMachine(c *check.C) {
DiscoKey: "faa",
Name: "testmachine",
NamespaceID: namespace.ID,
IPAddress: "10.0.0.1",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")},
Expiry: &now,
}
app.db.Save(&machine)
err = app.db.Save(&machine).Error
c.Assert(err, check.IsNil)
_, err = app.GetMachine("test", "testmachine")
_, err = app.GetMachine(namespace.Name, machine.Name)
c.Assert(err, check.IsNil)
machineAfterRegistering, err := app.RegisterMachine(
"8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
machine.MachineKey,
namespace.Name,
)
c.Assert(err, check.IsNil)

View file

@ -4,6 +4,7 @@ import (
"fmt"
"log"
"strconv"
"strings"
"time"
survey "github.com/AlecAivazis/survey/v2"
@ -459,7 +460,7 @@ func nodesToPtables(
"Name",
"NodeKey",
"Namespace",
"IP address",
"IP addresses",
"Ephemeral",
"Last seen",
"Online",
@ -523,7 +524,7 @@ func nodesToPtables(
machine.Name,
nodeKey.ShortString(),
namespace,
machine.IpAddress,
strings.Join(machine.IpAddresses, ", "),
strconv.FormatBool(ephemeral),
lastSeenTime,
online,

View file

@ -48,8 +48,6 @@ func LoadConfig(path string) error {
viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01")
viper.SetDefault("tls_client_auth_mode", "relaxed")
viper.SetDefault("ip_prefix", "100.64.0.0/10")
viper.SetDefault("log_level", "info")
viper.SetDefault("dns_config", nil)
@ -235,10 +233,57 @@ func getHeadscaleConfig() headscale.Config {
dnsConfig, baseDomain := GetDNSConfig()
derpConfig := GetDERPConfig()
configuredPrefixes := viper.GetStringSlice("ip_prefixes")
parsedPrefixes := make([]netaddr.IPPrefix, 0, len(configuredPrefixes)+1)
legacyPrefixField := viper.GetString("ip_prefix")
if len(legacyPrefixField) > 0 {
log.
Warn().
Msgf(
"%s, %s",
"use of 'ip_prefix' for configuration is deprecated",
"please see 'ip_prefixes' in the shipped example.",
)
legacyPrefix, err := netaddr.ParseIPPrefix(legacyPrefixField)
if err != nil {
panic(fmt.Errorf("failed to parse ip_prefix: %w", err))
}
parsedPrefixes = append(parsedPrefixes, legacyPrefix)
}
for i, prefixInConfig := range configuredPrefixes {
prefix, err := netaddr.ParseIPPrefix(prefixInConfig)
if err != nil {
panic(fmt.Errorf("failed to parse ip_prefixes[%d]: %w", i, err))
}
parsedPrefixes = append(parsedPrefixes, prefix)
}
prefixes := make([]netaddr.IPPrefix, 0, len(parsedPrefixes))
{
// dedup
normalizedPrefixes := make(map[string]int, len(parsedPrefixes))
for i, p := range parsedPrefixes {
normalized, _ := p.Range().Prefix()
normalizedPrefixes[normalized.String()] = i
}
// convert back to list
for _, i := range normalizedPrefixes {
prefixes = append(prefixes, parsedPrefixes[i])
}
}
if len(prefixes) < 1 {
prefixes = append(prefixes, netaddr.MustParseIPPrefix("100.64.0.0/10"))
log.Warn().Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes)
}
return headscale.Config{
ServerURL: viper.GetString("server_url"),
Addr: viper.GetString("listen_addr"),
IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
IPPrefixes: prefixes,
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
BaseDomain: baseDomain,

View file

@ -22,6 +22,13 @@ listen_addr: 0.0.0.0:8080
# autogenerated if it's missing
private_key_path: /var/lib/headscale/private.key
# List of IP prefixes to allocate tailaddresses from.
# Each prefix consists of either an IPv4 or IPv6 address,
# and the associated prefix length, delimited by a slash.
ip_prefixes:
- fd7a:115c:a1e0::/48
- 100.64.0.0/10
# DERP is a relay system that Tailscale uses when a direct
# connection cannot be established.
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp

8
db.go
View file

@ -28,20 +28,26 @@ func (h *Headscale) initDB() error {
h.db = db
if h.dbType == Postgres {
db.Exec("create extension if not exists \"uuid-ossp\";")
db.Exec(`create extension if not exists "uuid-ossp";`)
}
_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
err = db.AutoMigrate(&Machine{})
if err != nil {
return err
}
err = db.AutoMigrate(&KV{})
if err != nil {
return err
}
err = db.AutoMigrate(&Namespace{})
if err != nil {
return err
}
err = db.AutoMigrate(&PreAuthKey{})
if err != nil {
return err

82
dns.go
View file

@ -14,6 +14,11 @@ const (
ByteSize = 8
)
const (
ipv4AddressLength = 32
ipv6AddressLength = 128
)
// 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.
@ -34,14 +39,28 @@ const (
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
func generateMagicDNSRootDomains(
ipPrefix netaddr.IPPrefix,
) []dnsname.FQDN {
// TODO(juanfont): we are not handing out IPv6 addresses yet
// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
fqdns := []dnsname.FQDN{ipv6base}
func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN {
fqdns := make([]dnsname.FQDN, 0, len(ipPrefixes))
for _, ipPrefix := range ipPrefixes {
var generateDNSRoot func(netaddr.IPPrefix) []dnsname.FQDN
switch ipPrefix.IP().BitLen() {
case ipv4AddressLength:
generateDNSRoot = generateIPv4DNSRootDomain
case ipv6AddressLength:
generateDNSRoot = generateIPv6DNSRootDomain
default:
panic(fmt.Sprintf("unsupported IP version with address length %d", ipPrefix.IP().BitLen()))
}
fqdns = append(fqdns, generateDNSRoot(ipPrefix)...)
}
return fqdns
}
func generateIPv4DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
// Conversion to the std lib net.IPnet, a bit easier to operate
netRange := ipPrefix.IPNet()
maskBits, _ := netRange.Mask.Size()
@ -65,6 +84,7 @@ func generateMagicDNSRootDomains(
rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
rdnsBase := strings.Join(rdnsSlice, ".")
fqdns := make([]dnsname.FQDN, 0, max-min+1)
for i := min; i <= max; i++ {
fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%d.%s", i, rdnsBase))
if err != nil {
@ -76,6 +96,54 @@ func generateMagicDNSRootDomains(
return fqdns
}
func generateIPv6DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
const nibbleLen = 4
maskBits, _ := ipPrefix.IPNet().Mask.Size()
expanded := ipPrefix.IP().StringExpanded()
nibbleStr := strings.Map(func(r rune) rune {
if r == ':' {
return -1
}
return r
}, expanded)
// TODO?: that does not look the most efficient implementation,
// but the inputs are not so long as to cause problems,
// and from what I can see, the generateMagicDNSRootDomains
// function is called only once over the lifetime of a server process.
prefixConstantParts := []string{}
for i := 0; i < maskBits/nibbleLen; i++ {
prefixConstantParts = append([]string{string(nibbleStr[i])}, prefixConstantParts...)
}
makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) {
prefix := strings.Join(append(variablePrefix, prefixConstantParts...), ".")
return dnsname.ToFQDN(fmt.Sprintf("%s.ip6.arpa", prefix))
}
var fqdns []dnsname.FQDN
if maskBits%4 == 0 {
dom, _ := makeDomain()
fqdns = append(fqdns, dom)
} else {
domCount := 1 << (maskBits % nibbleLen)
fqdns = make([]dnsname.FQDN, 0, domCount)
for i := 0; i < domCount; i++ {
varNibble := fmt.Sprintf("%x", i)
dom, err := makeDomain(varNibble)
if err != nil {
continue
}
fqdns = append(fqdns, dom)
}
}
return fqdns
}
func getMapResponseDNSConfig(
dnsConfigOrig *tailcfg.DNSConfig,
baseDomain string,

View file

@ -10,8 +10,10 @@ import (
)
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
prefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
domains := generateMagicDNSRootDomains(prefix)
prefixes := []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.0.0/10"),
}
domains := generateMagicDNSRootDomains(prefixes)
found := false
for _, domain := range domains {
@ -45,8 +47,10 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
}
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
prefix := netaddr.MustParseIPPrefix("172.16.0.0/16")
domains := generateMagicDNSRootDomains(prefix)
prefixes := []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("172.16.0.0/16"),
}
domains := generateMagicDNSRootDomains(prefixes)
found := false
for _, domain := range domains {
@ -69,6 +73,40 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
c.Assert(found, check.Equals, true)
}
// Happens when netmask is a multiple of 4 bits (sounds likely).
func (s *Suite) TestMagicDNSRootDomainsIPv6Single(c *check.C) {
prefixes := []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48"),
}
domains := generateMagicDNSRootDomains(prefixes)
c.Assert(len(domains), check.Equals, 1)
c.Assert(domains[0].WithTrailingDot(), check.Equals, "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
}
func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) {
prefixes := []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/50"),
}
domains := generateMagicDNSRootDomains(prefixes)
yieldsRoot := func(dom string) bool {
for _, candidate := range domains {
if candidate.WithTrailingDot() == dom {
return true
}
}
return false
}
c.Assert(len(domains), check.Equals, 4)
c.Assert(yieldsRoot("0.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
c.Assert(yieldsRoot("1.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
c.Assert(yieldsRoot("2.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
c.Assert(yieldsRoot("3.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
}
func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
namespaceShared1, err := app.CreateNamespace("shared1")
c.Assert(err, check.IsNil)
@ -124,7 +162,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
Namespace: *namespaceShared1,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.1",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
AuthKeyID: uint(preAuthKeyInShared1.ID),
}
app.db.Save(machineInShared1)
@ -142,7 +180,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
Namespace: *namespaceShared2,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.2",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
AuthKeyID: uint(preAuthKeyInShared2.ID),
}
app.db.Save(machineInShared2)
@ -160,7 +198,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
Namespace: *namespaceShared3,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.3",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
AuthKeyID: uint(preAuthKeyInShared3.ID),
}
app.db.Save(machineInShared3)
@ -178,7 +216,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
Namespace: *namespaceShared1,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.4",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
AuthKeyID: uint(PreAuthKey2InShared1.ID),
}
app.db.Save(machine2InShared1)
@ -273,7 +311,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
Namespace: *namespaceShared1,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.1",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
AuthKeyID: uint(preAuthKeyInShared1.ID),
}
app.db.Save(machineInShared1)
@ -291,7 +329,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
Namespace: *namespaceShared2,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.2",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
AuthKeyID: uint(preAuthKeyInShared2.ID),
}
app.db.Save(machineInShared2)
@ -309,7 +347,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
Namespace: *namespaceShared3,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.3",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
AuthKeyID: uint(preAuthKeyInShared3.ID),
}
app.db.Save(machineInShared3)
@ -327,7 +365,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
Namespace: *namespaceShared1,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.4",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
AuthKeyID: uint(preAuthKey2InShared1.ID),
}
app.db.Save(machine2InShared1)

View file

@ -44,7 +44,7 @@ touch /var/lib/headscale/db.sqlite
touch /etc/headscale/config.yaml
```
It is **strongly recommended** to copy and modifiy the [example configuration](../config-example.yaml)
It is **strongly recommended** to copy and modify the [example configuration](../config-example.yaml)
from the [headscale repository](../)
6. Start the headscale server:

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc v3.17.3
// source: headscale/v1/device.proto
package v1

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc v3.17.3
// source: headscale/v1/headscale.proto
package v1

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc v3.17.3
// source: headscale/v1/machine.proto
package v1
@ -82,7 +82,7 @@ type Machine struct {
MachineKey string `protobuf:"bytes,2,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"`
NodeKey string `protobuf:"bytes,3,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"`
DiscoKey string `protobuf:"bytes,4,opt,name=disco_key,json=discoKey,proto3" json:"disco_key,omitempty"`
IpAddress string `protobuf:"bytes,5,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"`
IpAddresses []string `protobuf:"bytes,5,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
Namespace *Namespace `protobuf:"bytes,7,opt,name=namespace,proto3" json:"namespace,omitempty"`
Registered bool `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"`
@ -154,11 +154,11 @@ func (x *Machine) GetDiscoKey() string {
return ""
}
func (x *Machine) GetIpAddress() string {
func (x *Machine) GetIpAddresses() []string {
if x != nil {
return x.IpAddress
return x.IpAddresses
}
return ""
return nil
}
func (x *Machine) GetName() string {
@ -1026,129 +1026,129 @@ var file_headscale_v1_machine_proto_rawDesc = []byte{
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b,
0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf9, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63,
0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63,
0x68, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f,
0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65,
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79,
0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a,
0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61,
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x67,
0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e,
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x37,
0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c,
0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f,
0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70,
0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x3a, 0x0a,
0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0d, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70,
0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65,
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a,
0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20,
0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72,
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72,
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c,
0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75,
0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63,
0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a,
0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c,
0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4a,
0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72,
0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65,
0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a,
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x22, 0x4a, 0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a,
0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x32,
0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x49, 0x64, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68,
0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x45, 0x78, 0x70,
0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
0x22, 0x48, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63,
0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65,
0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x45,
0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x15,
0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a,
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, 0x14, 0x4c,
0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x6d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09,
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47, 0x0a, 0x14, 0x53, 0x68,
0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68,
0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a,
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e,
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, 0x16, 0x55, 0x6e, 0x73,
0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63,
0x68, 0x69, 0x6e, 0x65, 0x22, 0x77, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69,
0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18,
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x4d, 0x0a,
0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68,
0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68,
0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2a, 0x82, 0x01, 0x0a,
0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48,
0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54,
0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17,
0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f,
0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53,
0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10,
0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22,
0x49, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68,
0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47,
0x0a, 0x14, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07,
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61,
0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12,
0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a,
0x16, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x77, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x75,
0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x73, 0x22, 0x4d, 0x0a, 0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f,
0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52,
0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59,
0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d,
0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52,
0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f,
0x49, 0x44, 0x43, 0x10, 0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc v3.17.3
// source: headscale/v1/namespace.proto
package v1

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc v3.17.3
// source: headscale/v1/preauthkey.proto
package v1

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc v3.17.3
// source: headscale/v1/routes.proto
package v1

View file

@ -775,8 +775,11 @@
"discoKey": {
"type": "string"
},
"ipAddress": {
"type": "string"
"ipAddresses": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"

View file

@ -8,22 +8,48 @@ import (
"fmt"
"time"
"inet.af/netaddr"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
)
const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
var IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10")
var IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
type ExecuteCommandConfig struct {
timeout time.Duration
}
type ExecuteCommandOption func(*ExecuteCommandConfig) error
func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption {
return ExecuteCommandOption(func(conf *ExecuteCommandConfig) error {
conf.timeout = timeout
return nil
})
}
func ExecuteCommand(
resource *dockertest.Resource,
cmd []string,
env []string,
options ...ExecuteCommandOption,
) (string, error) {
var stdout bytes.Buffer
var stderr bytes.Buffer
// TODO(kradalby): Make configurable
timeout := DOCKER_EXECUTE_TIMEOUT
execConfig := ExecuteCommandConfig{
timeout: DOCKER_EXECUTE_TIMEOUT,
}
for _, opt := range options {
if err := opt(&execConfig); err != nil {
return "", fmt.Errorf("execute-command/options: %w", err)
}
}
type result struct {
exitCode int
@ -62,16 +88,33 @@ func ExecuteCommand(
}
return stdout.String(), nil
case <-time.After(timeout):
case <-time.After(execConfig.timeout):
return "", fmt.Errorf("command timed out after %s", timeout)
return "", fmt.Errorf("command timed out after %s", execConfig.timeout)
}
}
func DockerRestartPolicy(config *docker.HostConfig) {
// set AutoRemove to true so that stopped container goes away by itself
config.AutoRemove = true
// set AutoRemove to true so that stopped container goes away by itself on error *immediately*.
// when set to false, containers remain until the end of the integration test.
config.AutoRemove = false
config.RestartPolicy = docker.RestartPolicy{
Name: "no",
}
}
func DockerAllowLocalIPv6(config *docker.HostConfig) {
if config.Sysctls == nil {
config.Sysctls = make(map[string]string, 1)
}
config.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0"
}
func DockerAllowNetworkAdministration(config *docker.HostConfig) {
config.CapAdd = append(config.CapAdd, "NET_ADMIN")
config.Mounts = append(config.Mounts, docker.HostMount{
Type: "bind",
Source: "/dev/net/tun",
Target: "/dev/net/tun",
})
}

View file

@ -164,9 +164,7 @@ func (s *IntegrationTestSuite) tailscaleContainer(
Name: hostname,
Networks: []*dockertest.Network{&s.network},
Cmd: []string{
"tailscaled",
"--tun=userspace-networking",
"--socks5-server=localhost:1055",
"tailscaled", "--tun=tsdev",
},
}
@ -174,6 +172,8 @@ func (s *IntegrationTestSuite) tailscaleContainer(
tailscaleBuildOptions,
tailscaleOptions,
DockerRestartPolicy,
DockerAllowLocalIPv6,
DockerAllowNetworkAdministration,
)
if err != nil {
log.Fatalf("Could not start resource: %s", err)
@ -372,70 +372,74 @@ func (s *IntegrationTestSuite) TestListNodes() {
func (s *IntegrationTestSuite) TestGetIpAddresses() {
for _, scales := range s.namespaces {
ipPrefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
ips, err := getIPs(scales.tailscales)
assert.Nil(s.T(), err)
for hostname := range scales.tailscales {
s.T().Run(hostname, func(t *testing.T) {
ip, ok := ips[hostname]
for hostname, _ := range scales.tailscales {
ips := ips[hostname]
for _, ip := range ips {
s.T().Run(hostname, func(t *testing.T) {
assert.NotNil(t, ip)
assert.True(t, ok)
assert.NotNil(t, ip)
fmt.Printf("IP for %s: %s\n", hostname, ip)
fmt.Printf("IP for %s: %s\n", hostname, ip)
// c.Assert(ip.Valid(), check.IsTrue)
assert.True(t, ip.Is4())
assert.True(t, ipPrefix.Contains(ip))
})
// c.Assert(ip.Valid(), check.IsTrue)
assert.True(t, ip.Is4() || ip.Is6())
switch {
case ip.Is4():
assert.True(t, IpPrefix4.Contains(ip))
case ip.Is6():
assert.True(t, IpPrefix6.Contains(ip))
}
})
}
}
}
}
// TODO(kradalby): fix this test
// We need some way to impot ipnstate.Status from multiple go packages.
// We need some way to import ipnstate.Status from multiple go packages.
// Currently it will only work with 1.18.x since that is the last
// version we have in go.mod
// func (s *IntegrationTestSuite) TestStatus() {
// for _, scales := range s.namespaces {
// ips, err := getIPs(scales.tailscales)
// assert.Nil(s.T(), err)
// for _, scales := range s.namespaces {
// ips, err := getIPs(scales.tailscales)
// assert.Nil(s.T(), err)
//
// for hostname, tailscale := range scales.tailscales {
// s.T().Run(hostname, func(t *testing.T) {
// command := []string{"tailscale", "status", "--json"}
// for hostname, tailscale := range scales.tailscales {
// s.T().Run(hostname, func(t *testing.T) {
// command := []string{"tailscale", "status", "--json"}
//
// fmt.Printf("Getting status for %s\n", hostname)
// result, err := ExecuteCommand(
// &tailscale,
// command,
// []string{},
// )
// assert.Nil(t, err)
// fmt.Printf("Getting status for %s\n", hostname)
// result, err := ExecuteCommand(
// &tailscale,
// command,
// []string{},
// )
// assert.Nil(t, err)
//
// var status ipnstate.Status
// err = json.Unmarshal([]byte(result), &status)
// assert.Nil(s.T(), err)
// var status ipnstate.Status
// err = json.Unmarshal([]byte(result), &status)
// assert.Nil(s.T(), err)
//
// // TODO(kradalby): Replace this check with peer length of SAME namespace
// // Check if we have as many nodes in status
// // as we have IPs/tailscales
// // lines := strings.Split(result, "\n")
// // assert.Equal(t, len(ips), len(lines)-1)
// // assert.Equal(t, len(scales.tailscales), len(lines)-1)
// // TODO(kradalby): Replace this check with peer length of SAME namespace
// // Check if we have as many nodes in status
// // as we have IPs/tailscales
// // lines := strings.Split(result, "\n")
// // assert.Equal(t, len(ips), len(lines)-1)
// // assert.Equal(t, len(scales.tailscales), len(lines)-1)
//
// peerIps := getIPsfromIPNstate(status)
// peerIps := getIPsfromIPNstate(status)
//
// // Check that all hosts is present in all hosts status
// for ipHostname, ip := range ips {
// if hostname != ipHostname {
// assert.Contains(t, peerIps, ip)
// }
// }
// })
// }
// }
// // Check that all hosts is present in all hosts status
// for ipHostname, ip := range ips {
// if hostname != ipHostname {
// assert.Contains(t, peerIps, ip)
// }
// }
// })
// }
// }
// }
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
@ -448,16 +452,19 @@ func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
return ips
}
func (s *IntegrationTestSuite) TestPingAllPeers() {
func (s *IntegrationTestSuite) TestPingAllPeersByAddress() {
for _, scales := range s.namespaces {
ips, err := getIPs(scales.tailscales)
assert.Nil(s.T(), err)
for hostname, tailscale := range scales.tailscales {
for peername, ip := range ips {
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
for peername, peerIPs := range ips {
for i, ip := range peerIPs {
// We currently cant ping ourselves, so skip that.
if peername != hostname {
if peername == hostname {
continue
}
s.T().Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) {
// We are only interested in "direct ping" which means what we
// might need a couple of more attempts before reaching the node.
command := []string{
@ -469,9 +476,8 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
}
fmt.Printf(
"Pinging from %s (%s) to %s (%s)\n",
"Pinging from %s to %s (%s)\n",
hostname,
ips[hostname],
peername,
ip,
)
@ -483,8 +489,8 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", hostname, result)
assert.Contains(t, result, "pong")
}
})
})
}
}
}
}
@ -553,17 +559,17 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
// TODO(juanfont): We have to find out why do we need to wait
time.Sleep(100 * time.Second) // Wait for the nodes to receive updates
mainIps, err := getIPs(main.tailscales)
assert.Nil(s.T(), err)
sharedIps, err := getIPs(shared.tailscales)
assert.Nil(s.T(), err)
for hostname, tailscale := range main.tailscales {
for peername, ip := range sharedIps {
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
for peername, peerIPs := range sharedIps {
for i, ip := range peerIPs {
// We currently cant ping ourselves, so skip that.
if peername != hostname {
if peername == hostname {
continue
}
s.T().Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) {
// We are only interested in "direct ping" which means what we
// might need a couple of more attempts before reaching the node.
command := []string{
@ -575,9 +581,8 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
}
fmt.Printf(
"Pinging from %s (%s) to %s (%s)\n",
"Pinging from %s to %s (%s)\n",
hostname,
mainIps[hostname],
peername,
ip,
)
@ -589,8 +594,8 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", hostname, result)
assert.Contains(t, result, "pong")
}
})
})
}
}
}
}
@ -599,9 +604,19 @@ func (s *IntegrationTestSuite) TestTailDrop() {
for _, scales := range s.namespaces {
ips, err := getIPs(scales.tailscales)
assert.Nil(s.T(), err)
apiURLs, err := getAPIURLs(scales.tailscales)
assert.Nil(s.T(), err)
retry := func(times int, sleepInverval time.Duration, doWork func() error) (err error) {
for attempts := 0; attempts < times; attempts++ {
err = doWork()
if err == nil {
return
}
time.Sleep(sleepInverval)
}
return
}
for hostname, tailscale := range scales.tailscales {
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
_, err := ExecuteCommand(
@ -610,63 +625,31 @@ func (s *IntegrationTestSuite) TestTailDrop() {
[]string{},
)
assert.Nil(s.T(), err)
for peername, ip := range ips {
for peername, _ := range ips {
if peername == hostname {
continue
}
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
if peername != hostname {
// Under normal circumstances, we should be able to send a file
// using `tailscale file cp` - but not in userspace networking mode
// So curl!
peerAPI, ok := apiURLs[ip]
assert.True(t, ok)
// TODO(juanfont): We still have some issues with the test infrastructure, so
// lets run curl multiple times until it works.
attempts := 0
var err error
for {
command := []string{
"curl",
"--retry-connrefused",
"--retry-delay",
"30",
"--retry",
"10",
"--connect-timeout",
"60",
"-X",
"PUT",
"--upload-file",
fmt.Sprintf("/tmp/file_from_%s", hostname),
fmt.Sprintf(
"%s/v0/put/file_from_%s",
peerAPI,
hostname,
),
}
fmt.Printf(
"Sending file from %s (%s) to %s (%s)\n",
hostname,
ips[hostname],
peername,
ip,
)
_, err = ExecuteCommand(
&tailscale,
command,
[]string{"ALL_PROXY=socks5://localhost:1055"},
)
if err == nil {
break
} else {
time.Sleep(10 * time.Second)
attempts++
if attempts > 10 {
break
}
}
}
assert.Nil(t, err)
command := []string{
"tailscale", "file", "cp",
fmt.Sprintf("/tmp/file_from_%s", hostname),
fmt.Sprintf("%s:", peername),
}
retry(10, 1*time.Second, func() error {
fmt.Printf(
"Sending file from %s to %s\n",
hostname,
peername,
)
_, err := ExecuteCommand(
&tailscale,
command,
[]string{},
ExecuteCommandTimeout(60*time.Second),
)
return err
})
assert.Nil(t, err)
})
}
}
@ -684,32 +667,70 @@ func (s *IntegrationTestSuite) TestTailDrop() {
)
assert.Nil(s.T(), err)
for peername, ip := range ips {
if peername == hostname {
continue
}
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
if peername != hostname {
command := []string{
"ls",
fmt.Sprintf("/tmp/file_from_%s", peername),
}
fmt.Printf(
"Checking file in %s (%s) from %s (%s)\n",
hostname,
ips[hostname],
peername,
ip,
)
result, err := ExecuteCommand(
&tailscale,
command,
[]string{},
)
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", peername, result)
assert.Equal(
t,
result,
fmt.Sprintf("/tmp/file_from_%s\n", peername),
)
command := []string{
"ls",
fmt.Sprintf("/tmp/file_from_%s", peername),
}
fmt.Printf(
"Checking file in %s (%s) from %s (%s)\n",
hostname,
ips[hostname],
peername,
ip,
)
result, err := ExecuteCommand(
&tailscale,
command,
[]string{},
)
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", peername, result)
assert.Equal(
t,
fmt.Sprintf("/tmp/file_from_%s\n", peername),
result,
)
})
}
}
}
}
func (s *IntegrationTestSuite) TestPingAllPeersByHostname() {
for namespace, scales := range s.namespaces {
ips, err := getIPs(scales.tailscales)
assert.Nil(s.T(), err)
for hostname, tailscale := range scales.tailscales {
for peername, _ := range ips {
if peername == hostname {
continue
}
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
command := []string{
"tailscale", "ping",
"--timeout=10s",
"--c=20",
"--until-direct=true",
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
}
fmt.Printf(
"Pinging using hostname from %s to %s\n",
hostname,
peername,
)
result, err := ExecuteCommand(
&tailscale,
command,
[]string{},
)
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", hostname, result)
assert.Contains(t, result, "pong")
})
}
}
@ -721,32 +742,31 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
ips, err := getIPs(scales.tailscales)
assert.Nil(s.T(), err)
for hostname, tailscale := range scales.tailscales {
for peername, ip := range ips {
for peername, ips := range ips {
if peername == hostname {
continue
}
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
if peername != hostname {
command := []string{
"tailscale", "ping",
"--timeout=10s",
"--c=20",
"--until-direct=true",
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
}
command := []string{
"tailscale", "ip",
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
}
fmt.Printf(
"Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n",
hostname,
ips[hostname],
peername,
ip,
)
result, err := ExecuteCommand(
&tailscale,
command,
[]string{},
)
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", hostname, result)
assert.Contains(t, result, "pong")
fmt.Printf(
"Resolving name %s from %s\n",
peername,
hostname,
)
result, err := ExecuteCommand(
&tailscale,
command,
[]string{},
)
assert.Nil(t, err)
fmt.Printf("Result for %s: %s\n", hostname, result)
for _, ip := range ips {
assert.Contains(t, result, ip.String())
}
})
}
@ -754,8 +774,8 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
}
}
func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, error) {
ips := make(map[string]netaddr.IP)
func getIPs(tailscales map[string]dockertest.Resource) (map[string][]netaddr.IP, error) {
ips := make(map[string][]netaddr.IP)
for hostname, tailscale := range tailscales {
command := []string{"tailscale", "ip"}
@ -768,12 +788,17 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e
return nil, err
}
ip, err := netaddr.ParseIP(strings.TrimSuffix(result, "\n"))
if err != nil {
return nil, err
for _, address := range strings.Split(result, "\n") {
address = strings.TrimSuffix(address, "\n")
if len(address) < 1 {
continue
}
ip, err := netaddr.ParseIP(address)
if err != nil {
return nil, err
}
ips[hostname] = append(ips[hostname], ip)
}
ips[hostname] = ip
}
return ips, nil

View file

@ -2,6 +2,9 @@ log_level: trace
acl_policy_path: ""
db_type: sqlite3
ephemeral_node_inactivity_timeout: 30m
ip_prefixes:
- fd7a:115c:a1e0::/48
- 100.64.0.0/10
dns_config:
base_domain: headscale.net
magic_dns: true

View file

@ -1,6 +1,7 @@
package headscale
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
@ -23,6 +24,7 @@ const (
errMachineNotFound = Error("machine not found")
errMachineAlreadyRegistered = Error("machine already registered")
errMachineRouteIsNotAvailable = Error("route is not available on machine")
errMachineAddressesInvalid = Error("failed to parse machine addresses")
)
// Machine is a Headscale client.
@ -31,7 +33,7 @@ type Machine struct {
MachineKey string `gorm:"type:varchar(64);unique_index"`
NodeKey string
DiscoKey string
IPAddress string
IPAddresses MachineAddresses
Name string
NamespaceID uint
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
@ -64,6 +66,47 @@ func (machine Machine) isRegistered() bool {
return machine.Registered
}
type MachineAddresses []netaddr.IP
func (ma MachineAddresses) ToStringSlice() []string {
strSlice := make([]string, 0, len(ma))
for _, addr := range ma {
strSlice = append(strSlice, addr.String())
}
return strSlice
}
func (ma *MachineAddresses) Scan(destination interface{}) error {
switch value := destination.(type) {
case string:
addresses := strings.Split(value, ",")
*ma = (*ma)[:0]
for _, addr := range addresses {
if len(addr) < 1 {
continue
}
parsed, err := netaddr.ParseIP(addr)
if err != nil {
return err
}
*ma = append(*ma, parsed)
}
return nil
default:
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
}
}
// Value return json value, implement driver.Valuer interface.
func (ma MachineAddresses) Value() (driver.Value, error) {
addresses := strings.Join(ma.ToStringSlice(), ",")
return addresses, nil
}
// isExpired returns whether the machine registration has expired.
func (machine Machine) isExpired() bool {
// If Expiry is not set, the client has not indicated that
@ -385,14 +428,18 @@ func (h *Headscale) isOutdated(machine *Machine) bool {
}
lastChange := h.getLastStateChange(namespaces...)
lastUpdate := machine.CreatedAt
if machine.LastSuccessfulUpdate != nil {
lastUpdate = *machine.LastSuccessfulUpdate
}
log.Trace().
Caller().
Str("machine", machine.Name).
Time("last_successful_update", *machine.LastSuccessfulUpdate).
Time("last_state_change", lastChange).
Time("last_successful_update", lastChange).
Time("last_state_change", lastUpdate).
Msgf("Checking if %s is missing updates", machine.Name)
return machine.LastSuccessfulUpdate.Before(lastChange)
return lastUpdate.Before(lastChange)
}
func (machine Machine) String() string {
@ -478,22 +525,12 @@ func (machine Machine) toNode(
}
addrs := []netaddr.IPPrefix{}
ip, err := netaddr.ParseIPPrefix(fmt.Sprintf("%s/32", machine.IPAddress))
if err != nil {
log.Trace().
Caller().
Str("ip", machine.IPAddress).
Msgf("Failed to parse IP Prefix from IP: %s", machine.IPAddress)
return nil, err
for _, machineAddress := range machine.IPAddresses {
ip := netaddr.IPPrefixFrom(machineAddress, machineAddress.BitLen())
addrs = append(addrs, ip)
}
addrs = append(addrs, ip) // missing the ipv6 ?
allowedIPs := []netaddr.IPPrefix{}
allowedIPs = append(
allowedIPs,
ip,
) // we append the node own IP, as it is required by the clients
allowedIPs := append([]netaddr.IPPrefix{}, addrs...) // we append the node own IP, as it is required by the clients
if includeRoutes {
routesStr := []string{}
@ -600,11 +637,11 @@ func (machine *Machine) toProto() *v1.Machine {
Id: machine.ID,
MachineKey: machine.MachineKey,
NodeKey: machine.NodeKey,
DiscoKey: machine.DiscoKey,
IpAddress: machine.IPAddress,
Name: machine.Name,
Namespace: machine.Namespace.toProto(),
NodeKey: machine.NodeKey,
DiscoKey: machine.DiscoKey,
IpAddresses: machine.IPAddresses.ToStringSlice(),
Name: machine.Name,
Namespace: machine.Namespace.toProto(),
Registered: machine.Registered,
@ -703,7 +740,7 @@ func (h *Headscale) RegisterMachine(
return nil, err
}
ip, err := h.getAvailableIP()
ips, err := h.getAvailableIPs()
if err != nil {
log.Error().
Caller().
@ -717,10 +754,10 @@ func (h *Headscale) RegisterMachine(
log.Trace().
Caller().
Str("machine", machine.Name).
Str("ip", ip.String()).
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
Msg("Found IP for host")
machine.IPAddress = ip.String()
machine.IPAddresses = ips
machine.NamespaceID = namespace.ID
machine.Registered = true
machine.RegisterMethod = RegisterMethodCLI
@ -730,7 +767,7 @@ func (h *Headscale) RegisterMachine(
log.Trace().
Caller().
Str("machine", machine.Name).
Str("ip", ip.String()).
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
Msg("Machine registered with the database")
return machine, nil

View file

@ -6,6 +6,7 @@ import (
"time"
"gopkg.in/check.v1"
"inet.af/netaddr"
)
func (s *Suite) TestGetMachine(c *check.C) {
@ -199,3 +200,24 @@ func (s *Suite) TestExpireMachine(c *check.C) {
c.Assert(machineFromDB.isExpired(), check.Equals, true)
}
func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
input := MachineAddresses([]netaddr.IP{
netaddr.MustParseIP("192.0.2.1"),
netaddr.MustParseIP("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 MachineAddresses
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])
}
}

View file

@ -4,6 +4,7 @@ import (
"github.com/rs/zerolog/log"
"gopkg.in/check.v1"
"gorm.io/gorm"
"inet.af/netaddr"
)
func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
@ -146,7 +147,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
Namespace: *namespaceShared1,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.1",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
AuthKeyID: uint(preAuthKeyShared1.ID),
}
app.db.Save(machineInShared1)
@ -164,7 +165,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
Namespace: *namespaceShared2,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.2",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
AuthKeyID: uint(preAuthKeyShared2.ID),
}
app.db.Save(machineInShared2)
@ -182,7 +183,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
Namespace: *namespaceShared3,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.3",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
AuthKeyID: uint(preAuthKeyShared3.ID),
}
app.db.Save(machineInShared3)
@ -200,7 +201,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
Namespace: *namespaceShared1,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.4",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
AuthKeyID: uint(preAuthKey2Shared1.ID),
}
app.db.Save(machine2InShared1)

View file

@ -126,6 +126,7 @@ var oidcCallbackTemplate = template.Must(
</html>`),
)
// TODO: Why is the entire machine registration logic duplicated here?
// OIDCCallback handles the callback from the OIDC endpoint
// Retrieves the mkey from the state cache and adds the machine to the users email namespace
// TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
@ -316,7 +317,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
return
}
ip, err := h.getAvailableIP()
ips, err := h.getAvailableIPs()
if err != nil {
log.Error().
Caller().
@ -330,7 +331,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
return
}
machine.IPAddress = ip.String()
machine.IPAddresses = ips
machine.NamespaceID = namespace.ID
machine.Registered = true
machine.RegisterMethod = RegisterMethodOIDC

105
poll.go
View file

@ -1,8 +1,10 @@
package headscale
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
@ -154,14 +156,33 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
Str("id", ctx.Param("id")).
Str("machine", machine.Name).
Msg("Loading or creating update channel")
updateChan := make(chan struct{})
pollDataChan := make(chan []byte)
// TODO: could probably remove all that duplication once generics land.
closeChanWithLog := func(channel interface{}, name string) {
log.Trace().
Str("handler", "PollNetMap").
Str("machine", machine.Name).
Str("channel", "Done").
Msg(fmt.Sprintf("Closing %s channel", name))
switch c := channel.(type) {
case (chan struct{}):
close(c)
case (chan []byte):
close(c)
}
}
const chanSize = 8
updateChan := make(chan struct{}, chanSize)
defer closeChanWithLog(updateChan, "updateChan")
pollDataChan := make(chan []byte, chanSize)
defer closeChanWithLog(pollDataChan, "pollDataChan")
keepAliveChan := make(chan []byte)
cancelKeepAlive := make(chan struct{})
defer close(cancelKeepAlive)
defer closeChanWithLog(keepAliveChan, "keepAliveChan")
if req.OmitPeers && !req.Stream {
log.Info().
@ -174,7 +195,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
// even tho the comments in the tailscale code dont explicitly say so.
updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update").
Inc()
go func() { updateChan <- struct{}{} }()
updateChan <- struct{}{}
return
} else if req.OmitPeers && req.Stream {
@ -195,7 +216,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
Str("handler", "PollNetMap").
Str("machine", machine.Name).
Msg("Sending initial map")
go func() { pollDataChan <- data }()
pollDataChan <- data
log.Info().
Str("handler", "PollNetMap").
@ -203,7 +224,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
Msg("Notifying peers")
updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update").
Inc()
go func() { updateChan <- struct{}{} }()
updateChan <- struct{}{}
h.PollNetMapStream(
ctx,
@ -213,7 +234,6 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
pollDataChan,
keepAliveChan,
updateChan,
cancelKeepAlive,
)
log.Trace().
Str("handler", "PollNetMap").
@ -233,16 +253,20 @@ func (h *Headscale) PollNetMapStream(
pollDataChan chan []byte,
keepAliveChan chan []byte,
updateChan chan struct{},
cancelKeepAlive chan struct{},
) {
go h.scheduledPollWorker(
cancelKeepAlive,
updateChan,
keepAliveChan,
machineKey,
mapRequest,
machine,
)
{
ctx, cancel := context.WithCancel(ctx.Request.Context())
defer cancel()
go h.scheduledPollWorker(
ctx,
updateChan,
keepAliveChan,
machineKey,
mapRequest,
machine,
)
}
ctx.Stream(func(writer io.Writer) bool {
log.Trace().
@ -392,10 +416,14 @@ func (h *Headscale) PollNetMapStream(
updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name).
Inc()
if h.isOutdated(machine) {
var lastUpdate time.Time
if machine.LastSuccessfulUpdate != nil {
lastUpdate = *machine.LastSuccessfulUpdate
}
log.Debug().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Time("last_successful_update", *machine.LastSuccessfulUpdate).
Time("last_successful_update", lastUpdate).
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
Msgf("There has been updates since the last successful update to %s", machine.Name)
data, err := h.getMapResponse(machineKey, mapRequest, machine)
@ -464,10 +492,14 @@ func (h *Headscale) PollNetMapStream(
Msg("Cannot update machine LastSuccessfulUpdate")
}
} else {
var lastUpdate time.Time
if machine.LastSuccessfulUpdate != nil {
lastUpdate = *machine.LastSuccessfulUpdate
}
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Time("last_successful_update", *machine.LastSuccessfulUpdate).
Time("last_successful_update", lastUpdate).
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
Msgf("%s is up to date", machine.Name)
}
@ -507,42 +539,13 @@ func (h *Headscale) PollNetMapStream(
Msg("Cannot update machine LastSeen")
}
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "Done").
Msg("Cancelling keepAlive channel")
cancelKeepAlive <- struct{}{}
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "Done").
Msg("Closing update channel")
// h.closeUpdateChannel(m)
close(updateChan)
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "Done").
Msg("Closing pollData channel")
close(pollDataChan)
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "Done").
Msg("Closing keepAliveChan channel")
close(keepAliveChan)
return false
}
})
}
func (h *Headscale) scheduledPollWorker(
cancelChan <-chan struct{},
ctx context.Context,
updateChan chan<- struct{},
keepAliveChan chan<- []byte,
machineKey key.MachinePublic,
@ -554,7 +557,7 @@ func (h *Headscale) scheduledPollWorker(
for {
select {
case <-cancelChan:
case <-ctx.Done():
return
case <-keepAliveTicker.C:

View file

@ -18,7 +18,7 @@ message Machine {
string machine_key = 2;
string node_key = 3;
string disco_key = 4;
string ip_address = 5;
repeated string ip_addresses = 5;
string name = 6;
Namespace namespace = 7;

View file

@ -2,6 +2,7 @@ package headscale
import (
"gopkg.in/check.v1"
"inet.af/netaddr"
)
func CreateNodeNamespace(
@ -26,7 +27,7 @@ func CreateNodeNamespace(
NamespaceID: namespace.ID,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: ip,
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
AuthKeyID: uint(pak1.ID),
}
app.db.Save(machine)
@ -214,7 +215,7 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
NamespaceID: namespace1.ID,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.4",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
AuthKeyID: uint(pak4.ID),
}
app.db.Save(machine4)
@ -294,7 +295,7 @@ func (s *Suite) TestDeleteSharedMachine(c *check.C) {
NamespaceID: namespace1.ID,
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
IPAddress: "100.64.0.4",
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
AuthKeyID: uint(pak4n1.ID),
}
app.db.Save(machine4)

View file

@ -133,61 +133,78 @@ func encode(
return privKey.SealTo(*pubKey, b), nil
}
func (h *Headscale) getAvailableIP() (*netaddr.IP, error) {
ipPrefix := h.cfg.IPPrefix
func (h *Headscale) getAvailableIPs() (ips MachineAddresses, err error) {
ipPrefixes := h.cfg.IPPrefixes
for _, ipPrefix := range ipPrefixes {
var ip *netaddr.IP
ip, err = h.getAvailableIP(ipPrefix)
if err != nil {
return
}
ips = append(ips, *ip)
}
return
}
func GetIPPrefixEndpoints(na netaddr.IPPrefix) (network, broadcast netaddr.IP) {
ipRange := na.Range()
network = ipRange.From()
broadcast = ipRange.To()
return
}
// TODO: Is this concurrency safe?
// What would happen if multiple hosts were to register at the same time?
// Would we attempt to assign the same addresses to multiple nodes?
func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, error) {
usedIps, err := h.getUsedIPs()
if err != nil {
return nil, err
}
ipPrefixNetworkAddress, ipPrefixBroadcastAddress := GetIPPrefixEndpoints(ipPrefix)
// Get the first IP in our prefix
ip := ipPrefix.IP()
ip := ipPrefixNetworkAddress.Next()
for {
if !ipPrefix.Contains(ip) {
return nil, errCouldNotAllocateIP
}
// Some OS (including Linux) does not like when IPs ends with 0 or 255, which
// is typically called network or broadcast. Lets avoid them and continue
// to look when we get one of those traditionally reserved IPs.
ipRaw := ip.As4()
if ipRaw[3] == 0 || ipRaw[3] == 255 {
switch {
case ip.Compare(ipPrefixBroadcastAddress) == 0:
fallthrough
case containsIPs(usedIps, ip):
fallthrough
case ip.IsZero() || ip.IsLoopback():
ip = ip.Next()
continue
}
if ip.IsZero() &&
ip.IsLoopback() {
ip = ip.Next()
continue
}
if !containsIPs(usedIps, ip) {
default:
return &ip, nil
}
ip = ip.Next()
}
}
func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) {
var addresses []string
h.db.Model(&Machine{}).Pluck("ip_address", &addresses)
// FIXME: This really deserves a better data model,
// but this was quick to get running and it should be enough
// to begin experimenting with a dual stack tailnet.
var addressesSlices []string
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
ips := make([]netaddr.IP, len(addresses))
for index, addr := range addresses {
if addr != "" {
ip, err := netaddr.ParseIP(addr)
if err != nil {
return nil, fmt.Errorf("failed to parse ip from database: %w", err)
}
ips[index] = ip
ips := make([]netaddr.IP, 0, len(h.cfg.IPPrefixes)*len(addressesSlices))
for _, slice := range addressesSlices {
var a MachineAddresses
err := a.Scan(slice)
if err != nil {
return nil, fmt.Errorf("failed to read ip from database: %w", err)
}
ips = append(ips, a...)
}
return ips, nil

View file

@ -6,17 +6,18 @@ import (
)
func (s *Suite) TestGetAvailableIp(c *check.C) {
ip, err := app.getAvailableIP()
ips, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
expected := netaddr.MustParseIP("10.27.0.1")
c.Assert(ip.String(), check.Equals, expected.String())
c.Assert(len(ips), check.Equals, 1)
c.Assert(ips[0].String(), check.Equals, expected.String())
}
func (s *Suite) TestGetUsedIps(c *check.C) {
ip, err := app.getAvailableIP()
ips, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
namespace, err := app.CreateNamespace("test_ip")
@ -38,22 +39,24 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
AuthKeyID: uint(pak.ID),
IPAddress: ip.String(),
IPAddresses: ips,
}
app.db.Save(&machine)
ips, err := app.getUsedIPs()
usedIps, err := app.getUsedIPs()
c.Assert(err, check.IsNil)
expected := netaddr.MustParseIP("10.27.0.1")
c.Assert(ips[0], check.Equals, expected)
c.Assert(len(usedIps), check.Equals, 1)
c.Assert(usedIps[0], check.Equals, expected)
machine1, err := app.GetMachineByID(0)
c.Assert(err, check.IsNil)
c.Assert(machine1.IPAddress, check.Equals, expected.String())
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
c.Assert(machine1.IPAddresses[0], check.Equals, expected)
}
func (s *Suite) TestGetMultiIp(c *check.C) {
@ -61,7 +64,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
c.Assert(err, check.IsNil)
for index := 1; index <= 350; index++ {
ip, err := app.getAvailableIP()
ips, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
@ -80,59 +83,64 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
Registered: true,
RegisterMethod: RegisterMethodAuthKey,
AuthKeyID: uint(pak.ID),
IPAddress: ip.String(),
IPAddresses: ips,
}
app.db.Save(&machine)
}
ips, err := app.getUsedIPs()
usedIps, err := app.getUsedIPs()
c.Assert(err, check.IsNil)
c.Assert(len(ips), check.Equals, 350)
c.Assert(len(usedIps), check.Equals, 350)
c.Assert(ips[0], check.Equals, netaddr.MustParseIP("10.27.0.1"))
c.Assert(ips[9], check.Equals, netaddr.MustParseIP("10.27.0.10"))
c.Assert(ips[300], check.Equals, netaddr.MustParseIP("10.27.1.47"))
c.Assert(usedIps[0], check.Equals, netaddr.MustParseIP("10.27.0.1"))
c.Assert(usedIps[9], check.Equals, netaddr.MustParseIP("10.27.0.10"))
c.Assert(usedIps[300], check.Equals, netaddr.MustParseIP("10.27.1.45"))
// Check that we can read back the IPs
machine1, err := app.GetMachineByID(1)
c.Assert(err, check.IsNil)
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
c.Assert(
machine1.IPAddress,
machine1.IPAddresses[0],
check.Equals,
netaddr.MustParseIP("10.27.0.1").String(),
netaddr.MustParseIP("10.27.0.1"),
)
machine50, err := app.GetMachineByID(50)
c.Assert(err, check.IsNil)
c.Assert(len(machine50.IPAddresses), check.Equals, 1)
c.Assert(
machine50.IPAddress,
machine50.IPAddresses[0],
check.Equals,
netaddr.MustParseIP("10.27.0.50").String(),
netaddr.MustParseIP("10.27.0.50"),
)
expectedNextIP := netaddr.MustParseIP("10.27.1.97")
nextIP, err := app.getAvailableIP()
expectedNextIP := netaddr.MustParseIP("10.27.1.95")
nextIP, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
c.Assert(nextIP.String(), check.Equals, expectedNextIP.String())
c.Assert(len(nextIP), check.Equals, 1)
c.Assert(nextIP[0].String(), check.Equals, expectedNextIP.String())
// If we call get Available again, we should receive
// the same IP, as it has not been reserved.
nextIP2, err := app.getAvailableIP()
nextIP2, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
c.Assert(nextIP2.String(), check.Equals, expectedNextIP.String())
c.Assert(len(nextIP2), check.Equals, 1)
c.Assert(nextIP2[0].String(), check.Equals, expectedNextIP.String())
}
func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
ip, err := app.getAvailableIP()
ips, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
expected := netaddr.MustParseIP("10.27.0.1")
c.Assert(ip.String(), check.Equals, expected.String())
c.Assert(len(ips), check.Equals, 1)
c.Assert(ips[0].String(), check.Equals, expected.String())
namespace, err := app.CreateNamespace("test_ip")
c.Assert(err, check.IsNil)
@ -156,8 +164,9 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
}
app.db.Save(&machine)
ip2, err := app.getAvailableIP()
ips2, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
c.Assert(ip2.String(), check.Equals, expected.String())
c.Assert(len(ips2), check.Equals, 1)
c.Assert(ips2[0].String(), check.Equals, expected.String())
}