mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merge branch 'main' into configurable-mtls
This commit is contained in:
		
						commit
						168b1bd579
					
				
							
								
								
									
										16
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -14,22 +14,38 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - 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
 | 
					      - name: Setup Go
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: "1.17"
 | 
					          go-version: "1.17"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          go version
 | 
					          go version
 | 
				
			||||||
          sudo apt update
 | 
					          sudo apt update
 | 
				
			||||||
          sudo apt install -y make
 | 
					          sudo apt install -y make
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run build
 | 
					      - name: Run build
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        run: make build
 | 
					        run: make build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - uses: actions/upload-artifact@v2
 | 
					      - uses: actions/upload-artifact@v2
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: headscale-linux
 | 
					          name: headscale-linux
 | 
				
			||||||
          path: headscale
 | 
					          path: headscale
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										31
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							@ -8,8 +8,21 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - 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
 | 
					      - name: golangci-lint
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: golangci/golangci-lint-action@v2
 | 
					        uses: golangci/golangci-lint-action@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          version: latest
 | 
					          version: latest
 | 
				
			||||||
@ -24,8 +37,26 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - 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
 | 
					      - name: Prettify code
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: creyD/prettier_action@v4.0
 | 
					        uses: creyD/prettier_action@v4.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          prettier_options: >-
 | 
					          prettier_options: >-
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							@ -3,21 +3,30 @@ name: CI
 | 
				
			|||||||
on: [pull_request]
 | 
					on: [pull_request]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  # The "build" workflow
 | 
					 | 
				
			||||||
  integration-test:
 | 
					  integration-test:
 | 
				
			||||||
    # The type of runner that the job will run on
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Steps represent a sequence of tasks that will be executed as part of the job
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - 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
 | 
					      - name: Setup Go
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: "1.17"
 | 
					          go-version: "1.17"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run Integration tests
 | 
					      - name: Run Integration tests
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        run: go test -tags integration -timeout 30m
 | 
					        run: go test -tags integration -timeout 30m
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@ -3,31 +3,41 @@ name: CI
 | 
				
			|||||||
on: [push, pull_request]
 | 
					on: [push, pull_request]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  # The "build" workflow
 | 
					 | 
				
			||||||
  test:
 | 
					  test:
 | 
				
			||||||
    # The type of runner that the job will run on
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Steps represent a sequence of tasks that will be executed as part of the job
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - 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
 | 
					      - name: Setup Go
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        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
 | 
					      - name: Install dependencies
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          go version
 | 
					          go version
 | 
				
			||||||
          sudo apt update
 | 
					          sudo apt update
 | 
				
			||||||
          sudo apt install -y make
 | 
					          sudo apt install -y make
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run tests
 | 
					      - name: Run tests
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        run: make test
 | 
					        run: make test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run build
 | 
					      - name: Run build
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        run: make
 | 
					        run: make
 | 
				
			||||||
 | 
				
			|||||||
@ -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:
 | 
					before:
 | 
				
			||||||
  hooks:
 | 
					  hooks:
 | 
				
			||||||
    - go mod tidy
 | 
					    - go mod tidy -compat=1.17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
release:
 | 
					release:
 | 
				
			||||||
  prerelease: auto
 | 
					  prerelease: auto
 | 
				
			||||||
@ -33,7 +32,7 @@ builds:
 | 
				
			|||||||
    goarch:
 | 
					    goarch:
 | 
				
			||||||
      - arm
 | 
					      - arm
 | 
				
			||||||
    goarm:
 | 
					    goarm:
 | 
				
			||||||
      - 7
 | 
					      - "7"
 | 
				
			||||||
    env:
 | 
					    env:
 | 
				
			||||||
      - CC=arm-linux-gnueabihf-gcc
 | 
					      - CC=arm-linux-gnueabihf-gcc
 | 
				
			||||||
      - CXX=arm-linux-gnueabihf-g++
 | 
					      - CXX=arm-linux-gnueabihf-g++
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -2,6 +2,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
**TBD (TBD):**
 | 
					**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**:
 | 
					**Changes**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
 | 
					- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Builder image
 | 
					# Builder image
 | 
				
			||||||
FROM golang:1.17.6-bullseye AS build
 | 
					FROM docker.io/golang:1.17.1-bullseye AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Builder image
 | 
					# Builder image
 | 
				
			||||||
FROM golang:1.17.6-alpine AS build
 | 
					FROM docker.io/golang:1.17.1-alpine AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,7 +14,7 @@ RUN strip /go/bin/headscale
 | 
				
			|||||||
RUN test -e /go/bin/headscale
 | 
					RUN test -e /go/bin/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Production image
 | 
					# Production image
 | 
				
			||||||
FROM alpine:latest
 | 
					FROM docker.io/alpine:latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build /go/bin/headscale /bin/headscale
 | 
					COPY --from=build /go/bin/headscale /bin/headscale
 | 
				
			||||||
ENV TZ UTC
 | 
					ENV TZ UTC
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Builder image
 | 
					# Builder image
 | 
				
			||||||
FROM golang:1.17.1-bullseye AS build
 | 
					FROM docker.io/golang:1.17.1-bullseye AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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.gpg | apt-key add - \
 | 
				
			||||||
    && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \
 | 
					    && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \
 | 
				
			||||||
    && apt-get update \
 | 
					    && apt-get update \
 | 
				
			||||||
    && apt-get install -y tailscale=${TAILSCALE_VERSION} \
 | 
					    && apt-get install -y tailscale=${TAILSCALE_VERSION} dnsutils \
 | 
				
			||||||
    && rm -rf /var/lib/apt/lists/*
 | 
					    && rm -rf /var/lib/apt/lists/*
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@ -18,7 +18,7 @@ test:
 | 
				
			|||||||
	@go test -coverprofile=coverage.out ./...
 | 
						@go test -coverprofile=coverage.out ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_integration:
 | 
					test_integration:
 | 
				
			||||||
	go test -tags integration -timeout 30m ./...
 | 
						go test -tags integration -timeout 30m -count=1 ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_integration_cli:
 | 
					test_integration_cli:
 | 
				
			||||||
	go test -tags integration -v integration_cli_test.go integration_common_test.go
 | 
						go test -tags integration -v integration_cli_test.go integration_common_test.go
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								acls.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								acls.go
									
									
									
									
									
								
							@ -188,7 +188,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
 | 
				
			|||||||
				return nil, errInvalidNamespace
 | 
									return nil, errInvalidNamespace
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			for _, node := range nodes {
 | 
								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
 | 
									// FIXME: Check TagOwners allows this
 | 
				
			||||||
				for _, t := range hostinfo.RequestTags {
 | 
									for _, t := range hostinfo.RequestTags {
 | 
				
			||||||
					if alias[4:] == t {
 | 
										if alias[4:] == t {
 | 
				
			||||||
						ips = append(ips, machine.IPAddress)
 | 
											ips = append(ips, machine.IPAddresses.ToStringSlice()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						break
 | 
											break
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@ -241,7 +241,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		ips := []string{}
 | 
							ips := []string{}
 | 
				
			||||||
		for _, n := range nodes {
 | 
							for _, n := range nodes {
 | 
				
			||||||
			ips = append(ips, n.IPAddress)
 | 
								ips = append(ips, n.IPAddresses.ToStringSlice()...)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return ips, nil
 | 
							return ips, nil
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								acls_test.go
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								acls_test.go
									
									
									
									
									
								
							@ -61,9 +61,9 @@ func (s *Suite) TestPortRange(c *check.C) {
 | 
				
			|||||||
	c.Assert(rules, check.NotNil)
 | 
						c.Assert(rules, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(rules, check.HasLen, 1)
 | 
						c.Assert(rules, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts, 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.First, check.Equals, uint16(5400))
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
 | 
						c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestPortWildcard(c *check.C) {
 | 
					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.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(rules, check.HasLen, 1)
 | 
						c.Assert(rules, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts, 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.First, check.Equals, uint16(0))
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
 | 
						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, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].SrcIPs[0], check.Equals, "*")
 | 
						c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestPortNamespace(c *check.C) {
 | 
					func (s *Suite) TestPortNamespace(c *check.C) {
 | 
				
			||||||
@ -91,7 +91,7 @@ func (s *Suite) TestPortNamespace(c *check.C) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	_, err = app.GetMachine("testnamespace", "testmachine")
 | 
						_, err = app.GetMachine("testnamespace", "testmachine")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
	ip, _ := app.getAvailableIP()
 | 
						ips, _ := app.getAvailableIPs()
 | 
				
			||||||
	machine := Machine{
 | 
						machine := Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "foo",
 | 
							MachineKey:     "foo",
 | 
				
			||||||
@ -101,7 +101,7 @@ func (s *Suite) TestPortNamespace(c *check.C) {
 | 
				
			|||||||
		NamespaceID:    namespace.ID,
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      ip.String(),
 | 
							IPAddresses:    ips,
 | 
				
			||||||
		AuthKeyID:      uint(pak.ID),
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(&machine)
 | 
						app.db.Save(&machine)
 | 
				
			||||||
@ -116,12 +116,13 @@ func (s *Suite) TestPortNamespace(c *check.C) {
 | 
				
			|||||||
	c.Assert(rules, check.NotNil)
 | 
						c.Assert(rules, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(rules, check.HasLen, 1)
 | 
						c.Assert(rules, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts, 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.First, check.Equals, uint16(0))
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
 | 
						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, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
 | 
						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(len(ips), check.Equals, 1)
 | 
				
			||||||
 | 
						c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestPortGroup(c *check.C) {
 | 
					func (s *Suite) TestPortGroup(c *check.C) {
 | 
				
			||||||
@ -133,7 +134,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	_, err = app.GetMachine("testnamespace", "testmachine")
 | 
						_, err = app.GetMachine("testnamespace", "testmachine")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
	ip, _ := app.getAvailableIP()
 | 
						ips, _ := app.getAvailableIPs()
 | 
				
			||||||
	machine := Machine{
 | 
						machine := Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "foo",
 | 
							MachineKey:     "foo",
 | 
				
			||||||
@ -143,7 +144,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
 | 
				
			|||||||
		NamespaceID:    namespace.ID,
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      ip.String(),
 | 
							IPAddresses:    ips,
 | 
				
			||||||
		AuthKeyID:      uint(pak.ID),
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(&machine)
 | 
						app.db.Save(&machine)
 | 
				
			||||||
@ -156,10 +157,11 @@ func (s *Suite) TestPortGroup(c *check.C) {
 | 
				
			|||||||
	c.Assert(rules, check.NotNil)
 | 
						c.Assert(rules, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(rules, check.HasLen, 1)
 | 
						c.Assert(rules, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts, 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.First, check.Equals, uint16(0))
 | 
				
			||||||
	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
 | 
						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, check.HasLen, 1)
 | 
				
			||||||
	c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
 | 
						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(len(ips), check.Equals, 1)
 | 
				
			||||||
 | 
						c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								api.go
									
									
									
									
									
								
							@ -497,6 +497,7 @@ func (h *Headscale) handleMachineRegistrationNew(
 | 
				
			|||||||
	ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
 | 
						ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: check if any locks are needed around IP allocation.
 | 
				
			||||||
func (h *Headscale) handleAuthKey(
 | 
					func (h *Headscale) handleAuthKey(
 | 
				
			||||||
	ctx *gin.Context,
 | 
						ctx *gin.Context,
 | 
				
			||||||
	machineKey key.MachinePublic,
 | 
						machineKey key.MachinePublic,
 | 
				
			||||||
@ -554,14 +555,14 @@ func (h *Headscale) handleAuthKey(
 | 
				
			|||||||
		log.Debug().
 | 
							log.Debug().
 | 
				
			||||||
			Str("func", "handleAuthKey").
 | 
								Str("func", "handleAuthKey").
 | 
				
			||||||
			Str("machine", machine.Name).
 | 
								Str("machine", machine.Name).
 | 
				
			||||||
			Msg("Authentication key was valid, proceeding to acquire an IP address")
 | 
								Msg("Authentication key was valid, proceeding to acquire IP addresses")
 | 
				
			||||||
		ip, err := h.getAvailableIP()
 | 
							ips, err := h.getAvailableIPs()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error().
 | 
								log.Error().
 | 
				
			||||||
				Caller().
 | 
									Caller().
 | 
				
			||||||
				Str("func", "handleAuthKey").
 | 
									Str("func", "handleAuthKey").
 | 
				
			||||||
				Str("machine", machine.Name).
 | 
									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).
 | 
								machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
 | 
				
			||||||
				Inc()
 | 
									Inc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -570,12 +571,12 @@ func (h *Headscale) handleAuthKey(
 | 
				
			|||||||
		log.Info().
 | 
							log.Info().
 | 
				
			||||||
			Str("func", "handleAuthKey").
 | 
								Str("func", "handleAuthKey").
 | 
				
			||||||
			Str("machine", machine.Name).
 | 
								Str("machine", machine.Name).
 | 
				
			||||||
			Str("ip", ip.String()).
 | 
								Str("ips", strings.Join(ips.ToStringSlice(), ",")).
 | 
				
			||||||
			Msgf("Assigning %s to %s", ip, machine.Name)
 | 
								Msgf("Assigning %s to %s", strings.Join(ips.ToStringSlice(), ","), machine.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machine.Expiry = ®isterRequest.Expiry
 | 
							machine.Expiry = ®isterRequest.Expiry
 | 
				
			||||||
		machine.AuthKeyID = uint(pak.ID)
 | 
							machine.AuthKeyID = uint(pak.ID)
 | 
				
			||||||
		machine.IPAddress = ip.String()
 | 
							machine.IPAddresses = ips
 | 
				
			||||||
		machine.NamespaceID = pak.NamespaceID
 | 
							machine.NamespaceID = pak.NamespaceID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
 | 
							machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
 | 
				
			||||||
@ -610,6 +611,6 @@ func (h *Headscale) handleAuthKey(
 | 
				
			|||||||
	log.Info().
 | 
						log.Info().
 | 
				
			||||||
		Str("func", "handleAuthKey").
 | 
							Str("func", "handleAuthKey").
 | 
				
			||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Str("ip", machine.IPAddress).
 | 
							Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
 | 
				
			||||||
		Msg("Successfully authenticated via AuthKey")
 | 
							Msg("Successfully authenticated via AuthKey")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								app.go
									
									
									
									
									
								
							@ -73,7 +73,7 @@ type Config struct {
 | 
				
			|||||||
	ServerURL                      string
 | 
						ServerURL                      string
 | 
				
			||||||
	Addr                           string
 | 
						Addr                           string
 | 
				
			||||||
	EphemeralNodeInactivityTimeout time.Duration
 | 
						EphemeralNodeInactivityTimeout time.Duration
 | 
				
			||||||
	IPPrefix                       netaddr.IPPrefix
 | 
						IPPrefixes                     []netaddr.IPPrefix
 | 
				
			||||||
	PrivateKeyPath                 string
 | 
						PrivateKeyPath                 string
 | 
				
			||||||
	BaseDomain                     string
 | 
						BaseDomain                     string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -204,9 +204,7 @@ func NewHeadscale(cfg 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
 | 
				
			||||||
		magicDNSDomains := generateMagicDNSRootDomains(
 | 
							magicDNSDomains := generateMagicDNSRootDomains(app.cfg.IPPrefixes)
 | 
				
			||||||
			app.cfg.IPPrefix,
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		// 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)
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,9 @@ func (s *Suite) ResetDB(c *check.C) {
 | 
				
			|||||||
		c.Fatal(err)
 | 
							c.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cfg := Config{
 | 
						cfg := Config{
 | 
				
			||||||
		IPPrefix: netaddr.MustParseIPPrefix("10.27.0.0/23"),
 | 
							IPPrefixes: []netaddr.IPPrefix{
 | 
				
			||||||
 | 
								netaddr.MustParseIPPrefix("10.27.0.0/23"),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app = Headscale{
 | 
						app = Headscale{
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								cli_test.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								cli_test.go
									
									
									
									
									
								
							@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
 | 
						"inet.af/netaddr"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestRegisterMachine(c *check.C) {
 | 
					func (s *Suite) TestRegisterMachine(c *check.C) {
 | 
				
			||||||
@ -19,16 +20,17 @@ func (s *Suite) TestRegisterMachine(c *check.C) {
 | 
				
			|||||||
		DiscoKey:    "faa",
 | 
							DiscoKey:    "faa",
 | 
				
			||||||
		Name:        "testmachine",
 | 
							Name:        "testmachine",
 | 
				
			||||||
		NamespaceID: namespace.ID,
 | 
							NamespaceID: namespace.ID,
 | 
				
			||||||
		IPAddress:   "10.0.0.1",
 | 
							IPAddresses: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")},
 | 
				
			||||||
		Expiry:      &now,
 | 
							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)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machineAfterRegistering, err := app.RegisterMachine(
 | 
						machineAfterRegistering, err := app.RegisterMachine(
 | 
				
			||||||
		"8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
 | 
							machine.MachineKey,
 | 
				
			||||||
		namespace.Name,
 | 
							namespace.Name,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	survey "github.com/AlecAivazis/survey/v2"
 | 
						survey "github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
@ -459,7 +460,7 @@ func nodesToPtables(
 | 
				
			|||||||
			"Name",
 | 
								"Name",
 | 
				
			||||||
			"NodeKey",
 | 
								"NodeKey",
 | 
				
			||||||
			"Namespace",
 | 
								"Namespace",
 | 
				
			||||||
			"IP address",
 | 
								"IP addresses",
 | 
				
			||||||
			"Ephemeral",
 | 
								"Ephemeral",
 | 
				
			||||||
			"Last seen",
 | 
								"Last seen",
 | 
				
			||||||
			"Online",
 | 
								"Online",
 | 
				
			||||||
@ -523,7 +524,7 @@ func nodesToPtables(
 | 
				
			|||||||
				machine.Name,
 | 
									machine.Name,
 | 
				
			||||||
				nodeKey.ShortString(),
 | 
									nodeKey.ShortString(),
 | 
				
			||||||
				namespace,
 | 
									namespace,
 | 
				
			||||||
				machine.IpAddress,
 | 
									strings.Join(machine.IpAddresses, ", "),
 | 
				
			||||||
				strconv.FormatBool(ephemeral),
 | 
									strconv.FormatBool(ephemeral),
 | 
				
			||||||
				lastSeenTime,
 | 
									lastSeenTime,
 | 
				
			||||||
				online,
 | 
									online,
 | 
				
			||||||
 | 
				
			|||||||
@ -48,8 +48,6 @@ func LoadConfig(path string) error {
 | 
				
			|||||||
	viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01")
 | 
						viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01")
 | 
				
			||||||
	viper.SetDefault("tls_client_auth_mode", "relaxed")
 | 
						viper.SetDefault("tls_client_auth_mode", "relaxed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	viper.SetDefault("ip_prefix", "100.64.0.0/10")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	viper.SetDefault("log_level", "info")
 | 
						viper.SetDefault("log_level", "info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	viper.SetDefault("dns_config", nil)
 | 
						viper.SetDefault("dns_config", nil)
 | 
				
			||||||
@ -235,10 +233,57 @@ func getHeadscaleConfig() headscale.Config {
 | 
				
			|||||||
	dnsConfig, baseDomain := GetDNSConfig()
 | 
						dnsConfig, baseDomain := GetDNSConfig()
 | 
				
			||||||
	derpConfig := GetDERPConfig()
 | 
						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{
 | 
						return headscale.Config{
 | 
				
			||||||
		ServerURL:      viper.GetString("server_url"),
 | 
							ServerURL:      viper.GetString("server_url"),
 | 
				
			||||||
		Addr:           viper.GetString("listen_addr"),
 | 
							Addr:           viper.GetString("listen_addr"),
 | 
				
			||||||
		IPPrefix:       netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
 | 
							IPPrefixes:     prefixes,
 | 
				
			||||||
		PrivateKeyPath: absPath(viper.GetString("private_key_path")),
 | 
							PrivateKeyPath: absPath(viper.GetString("private_key_path")),
 | 
				
			||||||
		BaseDomain:     baseDomain,
 | 
							BaseDomain:     baseDomain,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,13 @@ listen_addr: 0.0.0.0:8080
 | 
				
			|||||||
# autogenerated if it's missing
 | 
					# autogenerated if it's missing
 | 
				
			||||||
private_key_path: /var/lib/headscale/private.key
 | 
					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
 | 
					# 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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								db.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								db.go
									
									
									
									
									
								
							@ -28,20 +28,26 @@ func (h *Headscale) initDB() error {
 | 
				
			|||||||
	h.db = db
 | 
						h.db = db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if h.dbType == Postgres {
 | 
						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{})
 | 
						err = db.AutoMigrate(&Machine{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = db.AutoMigrate(&KV{})
 | 
						err = db.AutoMigrate(&KV{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = db.AutoMigrate(&Namespace{})
 | 
						err = db.AutoMigrate(&Namespace{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = db.AutoMigrate(&PreAuthKey{})
 | 
						err = db.AutoMigrate(&PreAuthKey{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										82
									
								
								dns.go
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								dns.go
									
									
									
									
									
								
							@ -14,6 +14,11 @@ const (
 | 
				
			|||||||
	ByteSize = 8
 | 
						ByteSize = 8
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ipv4AddressLength = 32
 | 
				
			||||||
 | 
						ipv6AddressLength = 128
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
 | 
					// 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
 | 
					// 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.
 | 
					// 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).
 | 
					// 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(
 | 
					func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN {
 | 
				
			||||||
	ipPrefix netaddr.IPPrefix,
 | 
						fqdns := make([]dnsname.FQDN, 0, len(ipPrefixes))
 | 
				
			||||||
) []dnsname.FQDN {
 | 
						for _, ipPrefix := range ipPrefixes {
 | 
				
			||||||
	// TODO(juanfont): we are not handing out IPv6 addresses yet
 | 
							var generateDNSRoot func(netaddr.IPPrefix) []dnsname.FQDN
 | 
				
			||||||
	// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
 | 
							switch ipPrefix.IP().BitLen() {
 | 
				
			||||||
	ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
 | 
							case ipv4AddressLength:
 | 
				
			||||||
	fqdns := []dnsname.FQDN{ipv6base}
 | 
								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
 | 
						// Conversion to the std lib net.IPnet, a bit easier to operate
 | 
				
			||||||
	netRange := ipPrefix.IPNet()
 | 
						netRange := ipPrefix.IPNet()
 | 
				
			||||||
	maskBits, _ := netRange.Mask.Size()
 | 
						maskBits, _ := netRange.Mask.Size()
 | 
				
			||||||
@ -65,6 +84,7 @@ func generateMagicDNSRootDomains(
 | 
				
			|||||||
	rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
 | 
						rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
 | 
				
			||||||
	rdnsBase := strings.Join(rdnsSlice, ".")
 | 
						rdnsBase := strings.Join(rdnsSlice, ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fqdns := make([]dnsname.FQDN, 0, max-min+1)
 | 
				
			||||||
	for i := min; i <= max; i++ {
 | 
						for i := min; i <= max; i++ {
 | 
				
			||||||
		fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%d.%s", i, rdnsBase))
 | 
							fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%d.%s", i, rdnsBase))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -76,6 +96,54 @@ func generateMagicDNSRootDomains(
 | 
				
			|||||||
	return fqdns
 | 
						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(
 | 
					func getMapResponseDNSConfig(
 | 
				
			||||||
	dnsConfigOrig *tailcfg.DNSConfig,
 | 
						dnsConfigOrig *tailcfg.DNSConfig,
 | 
				
			||||||
	baseDomain string,
 | 
						baseDomain string,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										62
									
								
								dns_test.go
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								dns_test.go
									
									
									
									
									
								
							@ -10,8 +10,10 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
 | 
					func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
 | 
				
			||||||
	prefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
 | 
						prefixes := []netaddr.IPPrefix{
 | 
				
			||||||
	domains := generateMagicDNSRootDomains(prefix)
 | 
							netaddr.MustParseIPPrefix("100.64.0.0/10"),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						domains := generateMagicDNSRootDomains(prefixes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	found := false
 | 
						found := false
 | 
				
			||||||
	for _, domain := range domains {
 | 
						for _, domain := range domains {
 | 
				
			||||||
@ -45,8 +47,10 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
 | 
					func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
 | 
				
			||||||
	prefix := netaddr.MustParseIPPrefix("172.16.0.0/16")
 | 
						prefixes := []netaddr.IPPrefix{
 | 
				
			||||||
	domains := generateMagicDNSRootDomains(prefix)
 | 
							netaddr.MustParseIPPrefix("172.16.0.0/16"),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						domains := generateMagicDNSRootDomains(prefixes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	found := false
 | 
						found := false
 | 
				
			||||||
	for _, domain := range domains {
 | 
						for _, domain := range domains {
 | 
				
			||||||
@ -69,6 +73,40 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
 | 
				
			|||||||
	c.Assert(found, check.Equals, true)
 | 
						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) {
 | 
					func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 | 
				
			||||||
	namespaceShared1, err := app.CreateNamespace("shared1")
 | 
						namespaceShared1, err := app.CreateNamespace("shared1")
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
@ -124,7 +162,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared1,
 | 
							Namespace:      *namespaceShared1,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyInShared1.ID),
 | 
							AuthKeyID:      uint(preAuthKeyInShared1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared1)
 | 
						app.db.Save(machineInShared1)
 | 
				
			||||||
@ -142,7 +180,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared2,
 | 
							Namespace:      *namespaceShared2,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyInShared2.ID),
 | 
							AuthKeyID:      uint(preAuthKeyInShared2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared2)
 | 
						app.db.Save(machineInShared2)
 | 
				
			||||||
@ -160,7 +198,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared3,
 | 
							Namespace:      *namespaceShared3,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.3",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyInShared3.ID),
 | 
							AuthKeyID:      uint(preAuthKeyInShared3.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared3)
 | 
						app.db.Save(machineInShared3)
 | 
				
			||||||
@ -178,7 +216,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared1,
 | 
							Namespace:      *namespaceShared1,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.4",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 | 
				
			||||||
		AuthKeyID:      uint(PreAuthKey2InShared1.ID),
 | 
							AuthKeyID:      uint(PreAuthKey2InShared1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machine2InShared1)
 | 
						app.db.Save(machine2InShared1)
 | 
				
			||||||
@ -273,7 +311,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared1,
 | 
							Namespace:      *namespaceShared1,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyInShared1.ID),
 | 
							AuthKeyID:      uint(preAuthKeyInShared1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared1)
 | 
						app.db.Save(machineInShared1)
 | 
				
			||||||
@ -291,7 +329,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared2,
 | 
							Namespace:      *namespaceShared2,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyInShared2.ID),
 | 
							AuthKeyID:      uint(preAuthKeyInShared2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared2)
 | 
						app.db.Save(machineInShared2)
 | 
				
			||||||
@ -309,7 +347,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared3,
 | 
							Namespace:      *namespaceShared3,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.3",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyInShared3.ID),
 | 
							AuthKeyID:      uint(preAuthKeyInShared3.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared3)
 | 
						app.db.Save(machineInShared3)
 | 
				
			||||||
@ -327,7 +365,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared1,
 | 
							Namespace:      *namespaceShared1,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.4",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKey2InShared1.ID),
 | 
							AuthKeyID:      uint(preAuthKey2InShared1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machine2InShared1)
 | 
						app.db.Save(machine2InShared1)
 | 
				
			||||||
 | 
				
			|||||||
@ -44,7 +44,7 @@ touch /var/lib/headscale/db.sqlite
 | 
				
			|||||||
touch /etc/headscale/config.yaml
 | 
					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](../)
 | 
					from the [headscale repository](../)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
6. Start the headscale server:
 | 
					6. Start the headscale server:
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
// versions:
 | 
					// versions:
 | 
				
			||||||
// 	protoc-gen-go v1.27.1
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
// 	protoc        v3.18.1
 | 
					// 	protoc        v3.17.3
 | 
				
			||||||
// source: headscale/v1/device.proto
 | 
					// source: headscale/v1/device.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package v1
 | 
					package v1
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
// versions:
 | 
					// versions:
 | 
				
			||||||
// 	protoc-gen-go v1.27.1
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
// 	protoc        v3.18.1
 | 
					// 	protoc        v3.17.3
 | 
				
			||||||
// source: headscale/v1/headscale.proto
 | 
					// source: headscale/v1/headscale.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package v1
 | 
					package v1
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
// versions:
 | 
					// versions:
 | 
				
			||||||
// 	protoc-gen-go v1.27.1
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
// 	protoc        v3.18.1
 | 
					// 	protoc        v3.17.3
 | 
				
			||||||
// source: headscale/v1/machine.proto
 | 
					// source: headscale/v1/machine.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package v1
 | 
					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"`
 | 
						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"`
 | 
						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"`
 | 
						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"`
 | 
						Name                 string                 `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
 | 
				
			||||||
	Namespace            *Namespace             `protobuf:"bytes,7,opt,name=namespace,proto3" json:"namespace,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"`
 | 
						Registered           bool                   `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"`
 | 
				
			||||||
@ -154,11 +154,11 @@ func (x *Machine) GetDiscoKey() string {
 | 
				
			|||||||
	return ""
 | 
						return ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (x *Machine) GetIpAddress() string {
 | 
					func (x *Machine) GetIpAddresses() []string {
 | 
				
			||||||
	if x != nil {
 | 
						if x != nil {
 | 
				
			||||||
		return x.IpAddress
 | 
							return x.IpAddresses
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ""
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (x *Machine) GetName() string {
 | 
					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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a,
 | 
				
			||||||
	0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28,
 | 
						0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20,
 | 
				
			||||||
	0x09, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04,
 | 
						0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
 | 
				
			||||||
	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
 | 
						0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
 | 
				
			||||||
	0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20,
 | 
						0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
 | 
				
			||||||
	0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
 | 
						0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
 | 
				
			||||||
	0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61,
 | 
						0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
 | 
				
			||||||
	0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73,
 | 
						0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72,
 | 
				
			||||||
	0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x67,
 | 
						0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
 | 
				
			||||||
	0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73,
 | 
						0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72,
 | 
				
			||||||
	0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e,
 | 
						0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09,
 | 
				
			||||||
	0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
 | 
						0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
 | 
				
			||||||
	0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e,
 | 
						0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
 | 
				
			||||||
	0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x37,
 | 
						0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
 | 
				
			||||||
	0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28,
 | 
						0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18,
 | 
				
			||||||
	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 | 
						0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
 | 
				
			||||||
	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c,
 | 
						0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
 | 
				
			||||||
	0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f,
 | 
						0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c,
 | 
				
			||||||
	0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74,
 | 
						0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75,
 | 
				
			||||||
	0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
 | 
						0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
 | 
				
			||||||
	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
 | 
						0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
 | 
				
			||||||
	0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
 | 
						0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63,
 | 
				
			||||||
	0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70,
 | 
						0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a,
 | 
				
			||||||
	0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
 | 
						0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
 | 
				
			||||||
	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,
 | 
					 | 
				
			||||||
	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 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,
 | 
						0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72,
 | 
				
			||||||
	0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
 | 
						0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65,
 | 
				
			||||||
	0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c,
 | 
						0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
 | 
				
			||||||
	0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
 | 
						0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
 | 
				
			||||||
	0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03,
 | 
						0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a,
 | 
				
			||||||
	0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4a,
 | 
						0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28,
 | 
				
			||||||
	0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						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,
 | 
						0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69,
 | 
				
			||||||
	0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
 | 
						0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
 | 
				
			||||||
	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,
 | 
					 | 
				
			||||||
	0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01,
 | 
						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,
 | 
						0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22,
 | 
				
			||||||
	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
 | 
						0x49, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52,
 | 
				
			||||||
	0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
 | 
						0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69,
 | 
				
			||||||
	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18,
 | 
						0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64,
 | 
				
			||||||
	0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x4d, 0x0a,
 | 
						0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
 | 
				
			||||||
	0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68,
 | 
						0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68,
 | 
				
			||||||
	0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d,
 | 
						0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
 | 
				
			||||||
	0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68,
 | 
						0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
 | 
				
			||||||
	0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68,
 | 
						0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
 | 
				
			||||||
	0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2a, 0x82, 0x01, 0x0a,
 | 
						0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20,
 | 
				
			||||||
	0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
 | 
						0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47,
 | 
				
			||||||
	0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48,
 | 
						0x0a, 0x14, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
 | 
				
			||||||
	0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
 | 
						0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
 | 
				
			||||||
	0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54,
 | 
						0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
 | 
				
			||||||
	0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17,
 | 
						0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07,
 | 
				
			||||||
	0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f,
 | 
						0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61,
 | 
				
			||||||
	0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53,
 | 
						0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
 | 
				
			||||||
	0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10,
 | 
						0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
 | 
				
			||||||
	0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
 | 
						0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12,
 | 
				
			||||||
	0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
 | 
						0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
 | 
				
			||||||
	0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72,
 | 
						0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a,
 | 
				
			||||||
	0x6f, 0x74, 0x6f, 0x33,
 | 
						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 (
 | 
					var (
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
// versions:
 | 
					// versions:
 | 
				
			||||||
// 	protoc-gen-go v1.27.1
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
// 	protoc        v3.18.1
 | 
					// 	protoc        v3.17.3
 | 
				
			||||||
// source: headscale/v1/namespace.proto
 | 
					// source: headscale/v1/namespace.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package v1
 | 
					package v1
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
// versions:
 | 
					// versions:
 | 
				
			||||||
// 	protoc-gen-go v1.27.1
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
// 	protoc        v3.18.1
 | 
					// 	protoc        v3.17.3
 | 
				
			||||||
// source: headscale/v1/preauthkey.proto
 | 
					// source: headscale/v1/preauthkey.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package v1
 | 
					package v1
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
// versions:
 | 
					// versions:
 | 
				
			||||||
// 	protoc-gen-go v1.27.1
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
// 	protoc        v3.18.1
 | 
					// 	protoc        v3.17.3
 | 
				
			||||||
// source: headscale/v1/routes.proto
 | 
					// source: headscale/v1/routes.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package v1
 | 
					package v1
 | 
				
			||||||
 | 
				
			|||||||
@ -775,8 +775,11 @@
 | 
				
			|||||||
        "discoKey": {
 | 
					        "discoKey": {
 | 
				
			||||||
          "type": "string"
 | 
					          "type": "string"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "ipAddress": {
 | 
					        "ipAddresses": {
 | 
				
			||||||
          "type": "string"
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "type": "string"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "name": {
 | 
					        "name": {
 | 
				
			||||||
          "type": "string"
 | 
					          "type": "string"
 | 
				
			||||||
 | 
				
			|||||||
@ -8,22 +8,48 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"inet.af/netaddr"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/ory/dockertest/v3"
 | 
						"github.com/ory/dockertest/v3"
 | 
				
			||||||
	"github.com/ory/dockertest/v3/docker"
 | 
						"github.com/ory/dockertest/v3/docker"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
 | 
					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(
 | 
					func ExecuteCommand(
 | 
				
			||||||
	resource *dockertest.Resource,
 | 
						resource *dockertest.Resource,
 | 
				
			||||||
	cmd []string,
 | 
						cmd []string,
 | 
				
			||||||
	env []string,
 | 
						env []string,
 | 
				
			||||||
 | 
						options ...ExecuteCommandOption,
 | 
				
			||||||
) (string, error) {
 | 
					) (string, error) {
 | 
				
			||||||
	var stdout bytes.Buffer
 | 
						var stdout bytes.Buffer
 | 
				
			||||||
	var stderr bytes.Buffer
 | 
						var stderr bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO(kradalby): Make configurable
 | 
						execConfig := ExecuteCommandConfig{
 | 
				
			||||||
	timeout := DOCKER_EXECUTE_TIMEOUT
 | 
							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 {
 | 
						type result struct {
 | 
				
			||||||
		exitCode int
 | 
							exitCode int
 | 
				
			||||||
@ -62,16 +88,33 @@ func ExecuteCommand(
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return stdout.String(), nil
 | 
							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) {
 | 
					func DockerRestartPolicy(config *docker.HostConfig) {
 | 
				
			||||||
	// set AutoRemove to true so that stopped container goes away by itself
 | 
						// set AutoRemove to true so that stopped container goes away by itself on error *immediately*.
 | 
				
			||||||
	config.AutoRemove = true
 | 
						// when set to false, containers remain until the end of the integration test.
 | 
				
			||||||
 | 
						config.AutoRemove = false
 | 
				
			||||||
	config.RestartPolicy = docker.RestartPolicy{
 | 
						config.RestartPolicy = docker.RestartPolicy{
 | 
				
			||||||
		Name: "no",
 | 
							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",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -164,9 +164,7 @@ func (s *IntegrationTestSuite) tailscaleContainer(
 | 
				
			|||||||
		Name:     hostname,
 | 
							Name:     hostname,
 | 
				
			||||||
		Networks: []*dockertest.Network{&s.network},
 | 
							Networks: []*dockertest.Network{&s.network},
 | 
				
			||||||
		Cmd: []string{
 | 
							Cmd: []string{
 | 
				
			||||||
			"tailscaled",
 | 
								"tailscaled", "--tun=tsdev",
 | 
				
			||||||
			"--tun=userspace-networking",
 | 
					 | 
				
			||||||
			"--socks5-server=localhost:1055",
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -174,6 +172,8 @@ func (s *IntegrationTestSuite) tailscaleContainer(
 | 
				
			|||||||
		tailscaleBuildOptions,
 | 
							tailscaleBuildOptions,
 | 
				
			||||||
		tailscaleOptions,
 | 
							tailscaleOptions,
 | 
				
			||||||
		DockerRestartPolicy,
 | 
							DockerRestartPolicy,
 | 
				
			||||||
 | 
							DockerAllowLocalIPv6,
 | 
				
			||||||
 | 
							DockerAllowNetworkAdministration,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf("Could not start resource: %s", err)
 | 
							log.Fatalf("Could not start resource: %s", err)
 | 
				
			||||||
@ -372,70 +372,74 @@ func (s *IntegrationTestSuite) TestListNodes() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (s *IntegrationTestSuite) TestGetIpAddresses() {
 | 
					func (s *IntegrationTestSuite) TestGetIpAddresses() {
 | 
				
			||||||
	for _, scales := range s.namespaces {
 | 
						for _, scales := range s.namespaces {
 | 
				
			||||||
		ipPrefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
 | 
					 | 
				
			||||||
		ips, err := getIPs(scales.tailscales)
 | 
							ips, err := getIPs(scales.tailscales)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for hostname := range scales.tailscales {
 | 
							for hostname, _ := range scales.tailscales {
 | 
				
			||||||
			s.T().Run(hostname, func(t *testing.T) {
 | 
								ips := ips[hostname]
 | 
				
			||||||
				ip, ok := ips[hostname]
 | 
								for _, ip := range ips {
 | 
				
			||||||
 | 
									s.T().Run(hostname, func(t *testing.T) {
 | 
				
			||||||
 | 
										assert.NotNil(t, ip)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				assert.True(t, ok)
 | 
										fmt.Printf("IP for %s: %s\n", hostname, ip)
 | 
				
			||||||
				assert.NotNil(t, ip)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				fmt.Printf("IP for %s: %s\n", hostname, ip)
 | 
										// c.Assert(ip.Valid(), check.IsTrue)
 | 
				
			||||||
 | 
										assert.True(t, ip.Is4() || ip.Is6())
 | 
				
			||||||
				// c.Assert(ip.Valid(), check.IsTrue)
 | 
										switch {
 | 
				
			||||||
				assert.True(t, ip.Is4())
 | 
										case ip.Is4():
 | 
				
			||||||
				assert.True(t, ipPrefix.Contains(ip))
 | 
											assert.True(t, IpPrefix4.Contains(ip))
 | 
				
			||||||
			})
 | 
										case ip.Is6():
 | 
				
			||||||
 | 
											assert.True(t, IpPrefix6.Contains(ip))
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO(kradalby): fix this test
 | 
					// 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
 | 
					// Currently it will only work with 1.18.x since that is the last
 | 
				
			||||||
// version we have in go.mod
 | 
					// version we have in go.mod
 | 
				
			||||||
// func (s *IntegrationTestSuite) TestStatus() {
 | 
					// func (s *IntegrationTestSuite) TestStatus() {
 | 
				
			||||||
// 	for _, scales := range s.namespaces {
 | 
					//	for _, scales := range s.namespaces {
 | 
				
			||||||
// 		ips, err := getIPs(scales.tailscales)
 | 
					//		ips, err := getIPs(scales.tailscales)
 | 
				
			||||||
// 		assert.Nil(s.T(), err)
 | 
					//		assert.Nil(s.T(), err)
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// 		for hostname, tailscale := range scales.tailscales {
 | 
					//		for hostname, tailscale := range scales.tailscales {
 | 
				
			||||||
// 			s.T().Run(hostname, func(t *testing.T) {
 | 
					//			s.T().Run(hostname, func(t *testing.T) {
 | 
				
			||||||
// 				command := []string{"tailscale", "status", "--json"}
 | 
					//				command := []string{"tailscale", "status", "--json"}
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// 				fmt.Printf("Getting status for %s\n", hostname)
 | 
					//				fmt.Printf("Getting status for %s\n", hostname)
 | 
				
			||||||
// 				result, err := ExecuteCommand(
 | 
					//				result, err := ExecuteCommand(
 | 
				
			||||||
// 					&tailscale,
 | 
					//					&tailscale,
 | 
				
			||||||
// 					command,
 | 
					//					command,
 | 
				
			||||||
// 					[]string{},
 | 
					//					[]string{},
 | 
				
			||||||
// 				)
 | 
					//				)
 | 
				
			||||||
// 				assert.Nil(t, err)
 | 
					//				assert.Nil(t, err)
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// 				var status ipnstate.Status
 | 
					//				var status ipnstate.Status
 | 
				
			||||||
// 				err = json.Unmarshal([]byte(result), &status)
 | 
					//				err = json.Unmarshal([]byte(result), &status)
 | 
				
			||||||
// 				assert.Nil(s.T(), err)
 | 
					//				assert.Nil(s.T(), err)
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// 				// TODO(kradalby): Replace this check with peer length of SAME namespace
 | 
					//				// TODO(kradalby): Replace this check with peer length of SAME namespace
 | 
				
			||||||
// 				// Check if we have as many nodes in status
 | 
					//				// Check if we have as many nodes in status
 | 
				
			||||||
// 				// as we have IPs/tailscales
 | 
					//				// as we have IPs/tailscales
 | 
				
			||||||
// 				// lines := strings.Split(result, "\n")
 | 
					//				// lines := strings.Split(result, "\n")
 | 
				
			||||||
// 				// assert.Equal(t, len(ips), len(lines)-1)
 | 
					//				// assert.Equal(t, len(ips), len(lines)-1)
 | 
				
			||||||
// 				// assert.Equal(t, len(scales.tailscales), 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
 | 
					//				// Check that all hosts is present in all hosts status
 | 
				
			||||||
// 				for ipHostname, ip := range ips {
 | 
					//				for ipHostname, ip := range ips {
 | 
				
			||||||
// 					if hostname != ipHostname {
 | 
					//					if hostname != ipHostname {
 | 
				
			||||||
// 						assert.Contains(t, peerIps, ip)
 | 
					//						assert.Contains(t, peerIps, ip)
 | 
				
			||||||
// 					}
 | 
					//					}
 | 
				
			||||||
// 				}
 | 
					//				}
 | 
				
			||||||
// 			})
 | 
					//			})
 | 
				
			||||||
// 		}
 | 
					//		}
 | 
				
			||||||
// 	}
 | 
					//	}
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
 | 
					func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
 | 
				
			||||||
@ -448,16 +452,19 @@ func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
 | 
				
			|||||||
	return ips
 | 
						return ips
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *IntegrationTestSuite) TestPingAllPeers() {
 | 
					func (s *IntegrationTestSuite) TestPingAllPeersByAddress() {
 | 
				
			||||||
	for _, scales := range s.namespaces {
 | 
						for _, scales := range s.namespaces {
 | 
				
			||||||
		ips, err := getIPs(scales.tailscales)
 | 
							ips, err := getIPs(scales.tailscales)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for hostname, tailscale := range scales.tailscales {
 | 
							for hostname, tailscale := range scales.tailscales {
 | 
				
			||||||
			for peername, ip := range ips {
 | 
								for peername, peerIPs := range ips {
 | 
				
			||||||
				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
 | 
									for i, ip := range peerIPs {
 | 
				
			||||||
					// We currently cant ping ourselves, so skip that.
 | 
										// 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
 | 
											// We are only interested in "direct ping" which means what we
 | 
				
			||||||
						// might need a couple of more attempts before reaching the node.
 | 
											// might need a couple of more attempts before reaching the node.
 | 
				
			||||||
						command := []string{
 | 
											command := []string{
 | 
				
			||||||
@ -469,9 +476,8 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
 | 
				
			|||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						fmt.Printf(
 | 
											fmt.Printf(
 | 
				
			||||||
							"Pinging from %s (%s) to %s (%s)\n",
 | 
												"Pinging from %s to %s (%s)\n",
 | 
				
			||||||
							hostname,
 | 
												hostname,
 | 
				
			||||||
							ips[hostname],
 | 
					 | 
				
			||||||
							peername,
 | 
												peername,
 | 
				
			||||||
							ip,
 | 
												ip,
 | 
				
			||||||
						)
 | 
											)
 | 
				
			||||||
@ -483,8 +489,8 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
 | 
				
			|||||||
						assert.Nil(t, err)
 | 
											assert.Nil(t, err)
 | 
				
			||||||
						fmt.Printf("Result for %s: %s\n", hostname, result)
 | 
											fmt.Printf("Result for %s: %s\n", hostname, result)
 | 
				
			||||||
						assert.Contains(t, result, "pong")
 | 
											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
 | 
						// 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
 | 
						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)
 | 
						sharedIps, err := getIPs(shared.tailscales)
 | 
				
			||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for hostname, tailscale := range main.tailscales {
 | 
						for hostname, tailscale := range main.tailscales {
 | 
				
			||||||
		for peername, ip := range sharedIps {
 | 
							for peername, peerIPs := range sharedIps {
 | 
				
			||||||
			s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
 | 
								for i, ip := range peerIPs {
 | 
				
			||||||
				// We currently cant ping ourselves, so skip that.
 | 
									// 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
 | 
										// We are only interested in "direct ping" which means what we
 | 
				
			||||||
					// might need a couple of more attempts before reaching the node.
 | 
										// might need a couple of more attempts before reaching the node.
 | 
				
			||||||
					command := []string{
 | 
										command := []string{
 | 
				
			||||||
@ -575,9 +581,8 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					fmt.Printf(
 | 
										fmt.Printf(
 | 
				
			||||||
						"Pinging from %s (%s) to %s (%s)\n",
 | 
											"Pinging from %s to %s (%s)\n",
 | 
				
			||||||
						hostname,
 | 
											hostname,
 | 
				
			||||||
						mainIps[hostname],
 | 
					 | 
				
			||||||
						peername,
 | 
											peername,
 | 
				
			||||||
						ip,
 | 
											ip,
 | 
				
			||||||
					)
 | 
										)
 | 
				
			||||||
@ -589,8 +594,8 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
 | 
				
			|||||||
					assert.Nil(t, err)
 | 
										assert.Nil(t, err)
 | 
				
			||||||
					fmt.Printf("Result for %s: %s\n", hostname, result)
 | 
										fmt.Printf("Result for %s: %s\n", hostname, result)
 | 
				
			||||||
					assert.Contains(t, result, "pong")
 | 
										assert.Contains(t, result, "pong")
 | 
				
			||||||
				}
 | 
									})
 | 
				
			||||||
			})
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -599,9 +604,19 @@ func (s *IntegrationTestSuite) TestTailDrop() {
 | 
				
			|||||||
	for _, scales := range s.namespaces {
 | 
						for _, scales := range s.namespaces {
 | 
				
			||||||
		ips, err := getIPs(scales.tailscales)
 | 
							ips, err := getIPs(scales.tailscales)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
		apiURLs, err := getAPIURLs(scales.tailscales)
 | 
					 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							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 {
 | 
							for hostname, tailscale := range scales.tailscales {
 | 
				
			||||||
			command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
 | 
								command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
 | 
				
			||||||
			_, err := ExecuteCommand(
 | 
								_, err := ExecuteCommand(
 | 
				
			||||||
@ -610,63 +625,31 @@ func (s *IntegrationTestSuite) TestTailDrop() {
 | 
				
			|||||||
				[]string{},
 | 
									[]string{},
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
			assert.Nil(s.T(), err)
 | 
								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) {
 | 
									s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
 | 
				
			||||||
					if peername != hostname {
 | 
										command := []string{
 | 
				
			||||||
						// Under normal circumstances, we should be able to send a file
 | 
											"tailscale", "file", "cp",
 | 
				
			||||||
						// using `tailscale file cp` - but not in userspace networking mode
 | 
											fmt.Sprintf("/tmp/file_from_%s", hostname),
 | 
				
			||||||
						// So curl!
 | 
											fmt.Sprintf("%s:", peername),
 | 
				
			||||||
						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)
 | 
					 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
										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)
 | 
								assert.Nil(s.T(), err)
 | 
				
			||||||
			for peername, ip := range ips {
 | 
								for peername, ip := range ips {
 | 
				
			||||||
 | 
									if peername == hostname {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
 | 
									s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
 | 
				
			||||||
					if peername != hostname {
 | 
										command := []string{
 | 
				
			||||||
						command := []string{
 | 
											"ls",
 | 
				
			||||||
							"ls",
 | 
											fmt.Sprintf("/tmp/file_from_%s", peername),
 | 
				
			||||||
							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),
 | 
					 | 
				
			||||||
						)
 | 
					 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
										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)
 | 
							ips, err := getIPs(scales.tailscales)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
		for hostname, tailscale := range scales.tailscales {
 | 
							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) {
 | 
									s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
 | 
				
			||||||
					if peername != hostname {
 | 
										command := []string{
 | 
				
			||||||
						command := []string{
 | 
											"tailscale", "ip",
 | 
				
			||||||
							"tailscale", "ping",
 | 
											fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
 | 
				
			||||||
							"--timeout=10s",
 | 
										}
 | 
				
			||||||
							"--c=20",
 | 
					 | 
				
			||||||
							"--until-direct=true",
 | 
					 | 
				
			||||||
							fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
						fmt.Printf(
 | 
										fmt.Printf(
 | 
				
			||||||
							"Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n",
 | 
											"Resolving name %s from %s\n",
 | 
				
			||||||
							hostname,
 | 
											peername,
 | 
				
			||||||
							ips[hostname],
 | 
											hostname,
 | 
				
			||||||
							peername,
 | 
										)
 | 
				
			||||||
							ip,
 | 
										result, err := ExecuteCommand(
 | 
				
			||||||
						)
 | 
											&tailscale,
 | 
				
			||||||
						result, err := ExecuteCommand(
 | 
											command,
 | 
				
			||||||
							&tailscale,
 | 
											[]string{},
 | 
				
			||||||
							command,
 | 
										)
 | 
				
			||||||
							[]string{},
 | 
										assert.Nil(t, err)
 | 
				
			||||||
						)
 | 
										fmt.Printf("Result for %s: %s\n", hostname, result)
 | 
				
			||||||
						assert.Nil(t, err)
 | 
					
 | 
				
			||||||
						fmt.Printf("Result for %s: %s\n", hostname, result)
 | 
										for _, ip := range ips {
 | 
				
			||||||
						assert.Contains(t, result, "pong")
 | 
											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) {
 | 
					func getIPs(tailscales map[string]dockertest.Resource) (map[string][]netaddr.IP, error) {
 | 
				
			||||||
	ips := make(map[string]netaddr.IP)
 | 
						ips := make(map[string][]netaddr.IP)
 | 
				
			||||||
	for hostname, tailscale := range tailscales {
 | 
						for hostname, tailscale := range tailscales {
 | 
				
			||||||
		command := []string{"tailscale", "ip"}
 | 
							command := []string{"tailscale", "ip"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -768,12 +788,17 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e
 | 
				
			|||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ip, err := netaddr.ParseIP(strings.TrimSuffix(result, "\n"))
 | 
							for _, address := range strings.Split(result, "\n") {
 | 
				
			||||||
		if err != nil {
 | 
								address = strings.TrimSuffix(address, "\n")
 | 
				
			||||||
			return nil, err
 | 
								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
 | 
						return ips, nil
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,9 @@ log_level: trace
 | 
				
			|||||||
acl_policy_path: ""
 | 
					acl_policy_path: ""
 | 
				
			||||||
db_type: sqlite3
 | 
					db_type: sqlite3
 | 
				
			||||||
ephemeral_node_inactivity_timeout: 30m
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
					ip_prefixes:
 | 
				
			||||||
 | 
					  - fd7a:115c:a1e0::/48
 | 
				
			||||||
 | 
					  - 100.64.0.0/10
 | 
				
			||||||
dns_config:
 | 
					dns_config:
 | 
				
			||||||
  base_domain: headscale.net
 | 
					  base_domain: headscale.net
 | 
				
			||||||
  magic_dns: true
 | 
					  magic_dns: true
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										91
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								machine.go
									
									
									
									
									
								
							@ -1,6 +1,7 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"database/sql/driver"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@ -23,6 +24,7 @@ const (
 | 
				
			|||||||
	errMachineNotFound            = Error("machine not found")
 | 
						errMachineNotFound            = Error("machine not found")
 | 
				
			||||||
	errMachineAlreadyRegistered   = Error("machine already registered")
 | 
						errMachineAlreadyRegistered   = Error("machine already registered")
 | 
				
			||||||
	errMachineRouteIsNotAvailable = Error("route is not available on machine")
 | 
						errMachineRouteIsNotAvailable = Error("route is not available on machine")
 | 
				
			||||||
 | 
						errMachineAddressesInvalid    = Error("failed to parse machine addresses")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Machine is a Headscale client.
 | 
					// Machine is a Headscale client.
 | 
				
			||||||
@ -31,7 +33,7 @@ type Machine struct {
 | 
				
			|||||||
	MachineKey  string `gorm:"type:varchar(64);unique_index"`
 | 
						MachineKey  string `gorm:"type:varchar(64);unique_index"`
 | 
				
			||||||
	NodeKey     string
 | 
						NodeKey     string
 | 
				
			||||||
	DiscoKey    string
 | 
						DiscoKey    string
 | 
				
			||||||
	IPAddress   string
 | 
						IPAddresses MachineAddresses
 | 
				
			||||||
	Name        string
 | 
						Name        string
 | 
				
			||||||
	NamespaceID uint
 | 
						NamespaceID uint
 | 
				
			||||||
	Namespace   Namespace `gorm:"foreignKey:NamespaceID"`
 | 
						Namespace   Namespace `gorm:"foreignKey:NamespaceID"`
 | 
				
			||||||
@ -64,6 +66,47 @@ func (machine Machine) isRegistered() bool {
 | 
				
			|||||||
	return machine.Registered
 | 
						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.
 | 
					// isExpired returns whether the machine registration has expired.
 | 
				
			||||||
func (machine Machine) isExpired() bool {
 | 
					func (machine Machine) isExpired() bool {
 | 
				
			||||||
	// If Expiry is not set, the client has not indicated that
 | 
						// 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...)
 | 
						lastChange := h.getLastStateChange(namespaces...)
 | 
				
			||||||
 | 
						lastUpdate := machine.CreatedAt
 | 
				
			||||||
 | 
						if machine.LastSuccessfulUpdate != nil {
 | 
				
			||||||
 | 
							lastUpdate = *machine.LastSuccessfulUpdate
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Caller().
 | 
							Caller().
 | 
				
			||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Time("last_successful_update", *machine.LastSuccessfulUpdate).
 | 
							Time("last_successful_update", lastChange).
 | 
				
			||||||
		Time("last_state_change", lastChange).
 | 
							Time("last_state_change", lastUpdate).
 | 
				
			||||||
		Msgf("Checking if %s is missing updates", machine.Name)
 | 
							Msgf("Checking if %s is missing updates", machine.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return machine.LastSuccessfulUpdate.Before(lastChange)
 | 
						return lastUpdate.Before(lastChange)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (machine Machine) String() string {
 | 
					func (machine Machine) String() string {
 | 
				
			||||||
@ -478,22 +525,12 @@ func (machine Machine) toNode(
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	addrs := []netaddr.IPPrefix{}
 | 
						addrs := []netaddr.IPPrefix{}
 | 
				
			||||||
	ip, err := netaddr.ParseIPPrefix(fmt.Sprintf("%s/32", machine.IPAddress))
 | 
						for _, machineAddress := range machine.IPAddresses {
 | 
				
			||||||
	if err != nil {
 | 
							ip := netaddr.IPPrefixFrom(machineAddress, machineAddress.BitLen())
 | 
				
			||||||
		log.Trace().
 | 
							addrs = append(addrs, ip)
 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Str("ip", machine.IPAddress).
 | 
					 | 
				
			||||||
			Msgf("Failed to parse IP Prefix from IP: %s", machine.IPAddress)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	addrs = append(addrs, ip) // missing the ipv6 ?
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	allowedIPs := []netaddr.IPPrefix{}
 | 
						allowedIPs := append([]netaddr.IPPrefix{}, addrs...) // we append the node own IP, as it is required by the clients
 | 
				
			||||||
	allowedIPs = append(
 | 
					 | 
				
			||||||
		allowedIPs,
 | 
					 | 
				
			||||||
		ip,
 | 
					 | 
				
			||||||
	) // we append the node own IP, as it is required by the clients
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if includeRoutes {
 | 
						if includeRoutes {
 | 
				
			||||||
		routesStr := []string{}
 | 
							routesStr := []string{}
 | 
				
			||||||
@ -600,11 +637,11 @@ func (machine *Machine) toProto() *v1.Machine {
 | 
				
			|||||||
		Id:         machine.ID,
 | 
							Id:         machine.ID,
 | 
				
			||||||
		MachineKey: machine.MachineKey,
 | 
							MachineKey: machine.MachineKey,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		NodeKey:   machine.NodeKey,
 | 
							NodeKey:     machine.NodeKey,
 | 
				
			||||||
		DiscoKey:  machine.DiscoKey,
 | 
							DiscoKey:    machine.DiscoKey,
 | 
				
			||||||
		IpAddress: machine.IPAddress,
 | 
							IpAddresses: machine.IPAddresses.ToStringSlice(),
 | 
				
			||||||
		Name:      machine.Name,
 | 
							Name:        machine.Name,
 | 
				
			||||||
		Namespace: machine.Namespace.toProto(),
 | 
							Namespace:   machine.Namespace.toProto(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Registered: machine.Registered,
 | 
							Registered: machine.Registered,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -703,7 +740,7 @@ func (h *Headscale) RegisterMachine(
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ip, err := h.getAvailableIP()
 | 
						ips, err := h.getAvailableIPs()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
			Caller().
 | 
								Caller().
 | 
				
			||||||
@ -717,10 +754,10 @@ func (h *Headscale) RegisterMachine(
 | 
				
			|||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Caller().
 | 
							Caller().
 | 
				
			||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Str("ip", ip.String()).
 | 
							Str("ip", strings.Join(ips.ToStringSlice(), ",")).
 | 
				
			||||||
		Msg("Found IP for host")
 | 
							Msg("Found IP for host")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machine.IPAddress = ip.String()
 | 
						machine.IPAddresses = ips
 | 
				
			||||||
	machine.NamespaceID = namespace.ID
 | 
						machine.NamespaceID = namespace.ID
 | 
				
			||||||
	machine.Registered = true
 | 
						machine.Registered = true
 | 
				
			||||||
	machine.RegisterMethod = RegisterMethodCLI
 | 
						machine.RegisterMethod = RegisterMethodCLI
 | 
				
			||||||
@ -730,7 +767,7 @@ func (h *Headscale) RegisterMachine(
 | 
				
			|||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Caller().
 | 
							Caller().
 | 
				
			||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Str("ip", ip.String()).
 | 
							Str("ip", strings.Join(ips.ToStringSlice(), ",")).
 | 
				
			||||||
		Msg("Machine registered with the database")
 | 
							Msg("Machine registered with the database")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return machine, nil
 | 
						return machine, nil
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
 | 
						"inet.af/netaddr"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestGetMachine(c *check.C) {
 | 
					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)
 | 
						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])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
						"inet.af/netaddr"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
 | 
					func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
 | 
				
			||||||
@ -146,7 +147,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared1,
 | 
							Namespace:      *namespaceShared1,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyShared1.ID),
 | 
							AuthKeyID:      uint(preAuthKeyShared1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared1)
 | 
						app.db.Save(machineInShared1)
 | 
				
			||||||
@ -164,7 +165,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared2,
 | 
							Namespace:      *namespaceShared2,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyShared2.ID),
 | 
							AuthKeyID:      uint(preAuthKeyShared2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared2)
 | 
						app.db.Save(machineInShared2)
 | 
				
			||||||
@ -182,7 +183,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared3,
 | 
							Namespace:      *namespaceShared3,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.3",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKeyShared3.ID),
 | 
							AuthKeyID:      uint(preAuthKeyShared3.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machineInShared3)
 | 
						app.db.Save(machineInShared3)
 | 
				
			||||||
@ -200,7 +201,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 | 
				
			|||||||
		Namespace:      *namespaceShared1,
 | 
							Namespace:      *namespaceShared1,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.4",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 | 
				
			||||||
		AuthKeyID:      uint(preAuthKey2Shared1.ID),
 | 
							AuthKeyID:      uint(preAuthKey2Shared1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machine2InShared1)
 | 
						app.db.Save(machine2InShared1)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								oidc.go
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								oidc.go
									
									
									
									
									
								
							@ -126,6 +126,7 @@ var oidcCallbackTemplate = template.Must(
 | 
				
			|||||||
	</html>`),
 | 
						</html>`),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Why is the entire machine registration logic duplicated here?
 | 
				
			||||||
// OIDCCallback handles the callback from the OIDC endpoint
 | 
					// OIDCCallback handles the callback from the OIDC endpoint
 | 
				
			||||||
// Retrieves the mkey from the state cache and adds the machine to the users email namespace
 | 
					// 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
 | 
					// 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
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ip, err := h.getAvailableIP()
 | 
								ips, err := h.getAvailableIPs()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Caller().
 | 
										Caller().
 | 
				
			||||||
@ -330,7 +331,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			machine.IPAddress = ip.String()
 | 
								machine.IPAddresses = ips
 | 
				
			||||||
			machine.NamespaceID = namespace.ID
 | 
								machine.NamespaceID = namespace.ID
 | 
				
			||||||
			machine.Registered = true
 | 
								machine.Registered = true
 | 
				
			||||||
			machine.RegisterMethod = RegisterMethodOIDC
 | 
								machine.RegisterMethod = RegisterMethodOIDC
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										105
									
								
								poll.go
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								poll.go
									
									
									
									
									
								
							@ -1,8 +1,10 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@ -154,14 +156,33 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 | 
				
			|||||||
		Str("id", ctx.Param("id")).
 | 
							Str("id", ctx.Param("id")).
 | 
				
			||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Msg("Loading or creating update channel")
 | 
							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)
 | 
						keepAliveChan := make(chan []byte)
 | 
				
			||||||
 | 
						defer closeChanWithLog(keepAliveChan, "keepAliveChan")
 | 
				
			||||||
	cancelKeepAlive := make(chan struct{})
 | 
					 | 
				
			||||||
	defer close(cancelKeepAlive)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if req.OmitPeers && !req.Stream {
 | 
						if req.OmitPeers && !req.Stream {
 | 
				
			||||||
		log.Info().
 | 
							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.
 | 
							// even tho the comments in the tailscale code dont explicitly say so.
 | 
				
			||||||
		updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update").
 | 
							updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update").
 | 
				
			||||||
			Inc()
 | 
								Inc()
 | 
				
			||||||
		go func() { updateChan <- struct{}{} }()
 | 
							updateChan <- struct{}{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	} else if req.OmitPeers && req.Stream {
 | 
						} else if req.OmitPeers && req.Stream {
 | 
				
			||||||
@ -195,7 +216,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 | 
				
			|||||||
		Str("handler", "PollNetMap").
 | 
							Str("handler", "PollNetMap").
 | 
				
			||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Msg("Sending initial map")
 | 
							Msg("Sending initial map")
 | 
				
			||||||
	go func() { pollDataChan <- data }()
 | 
						pollDataChan <- data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Info().
 | 
						log.Info().
 | 
				
			||||||
		Str("handler", "PollNetMap").
 | 
							Str("handler", "PollNetMap").
 | 
				
			||||||
@ -203,7 +224,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 | 
				
			|||||||
		Msg("Notifying peers")
 | 
							Msg("Notifying peers")
 | 
				
			||||||
	updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update").
 | 
						updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update").
 | 
				
			||||||
		Inc()
 | 
							Inc()
 | 
				
			||||||
	go func() { updateChan <- struct{}{} }()
 | 
						updateChan <- struct{}{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h.PollNetMapStream(
 | 
						h.PollNetMapStream(
 | 
				
			||||||
		ctx,
 | 
							ctx,
 | 
				
			||||||
@ -213,7 +234,6 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 | 
				
			|||||||
		pollDataChan,
 | 
							pollDataChan,
 | 
				
			||||||
		keepAliveChan,
 | 
							keepAliveChan,
 | 
				
			||||||
		updateChan,
 | 
							updateChan,
 | 
				
			||||||
		cancelKeepAlive,
 | 
					 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Str("handler", "PollNetMap").
 | 
							Str("handler", "PollNetMap").
 | 
				
			||||||
@ -233,16 +253,20 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
	pollDataChan chan []byte,
 | 
						pollDataChan chan []byte,
 | 
				
			||||||
	keepAliveChan chan []byte,
 | 
						keepAliveChan chan []byte,
 | 
				
			||||||
	updateChan chan struct{},
 | 
						updateChan chan struct{},
 | 
				
			||||||
	cancelKeepAlive chan struct{},
 | 
					 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	go h.scheduledPollWorker(
 | 
						{
 | 
				
			||||||
		cancelKeepAlive,
 | 
							ctx, cancel := context.WithCancel(ctx.Request.Context())
 | 
				
			||||||
		updateChan,
 | 
							defer cancel()
 | 
				
			||||||
		keepAliveChan,
 | 
					
 | 
				
			||||||
		machineKey,
 | 
							go h.scheduledPollWorker(
 | 
				
			||||||
		mapRequest,
 | 
								ctx,
 | 
				
			||||||
		machine,
 | 
								updateChan,
 | 
				
			||||||
	)
 | 
								keepAliveChan,
 | 
				
			||||||
 | 
								machineKey,
 | 
				
			||||||
 | 
								mapRequest,
 | 
				
			||||||
 | 
								machine,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Stream(func(writer io.Writer) bool {
 | 
						ctx.Stream(func(writer io.Writer) bool {
 | 
				
			||||||
		log.Trace().
 | 
							log.Trace().
 | 
				
			||||||
@ -392,10 +416,14 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
			updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name).
 | 
								updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name).
 | 
				
			||||||
				Inc()
 | 
									Inc()
 | 
				
			||||||
			if h.isOutdated(machine) {
 | 
								if h.isOutdated(machine) {
 | 
				
			||||||
 | 
									var lastUpdate time.Time
 | 
				
			||||||
 | 
									if machine.LastSuccessfulUpdate != nil {
 | 
				
			||||||
 | 
										lastUpdate = *machine.LastSuccessfulUpdate
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				log.Debug().
 | 
									log.Debug().
 | 
				
			||||||
					Str("handler", "PollNetMapStream").
 | 
										Str("handler", "PollNetMapStream").
 | 
				
			||||||
					Str("machine", machine.Name).
 | 
										Str("machine", machine.Name).
 | 
				
			||||||
					Time("last_successful_update", *machine.LastSuccessfulUpdate).
 | 
										Time("last_successful_update", lastUpdate).
 | 
				
			||||||
					Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
 | 
										Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
 | 
				
			||||||
					Msgf("There has been updates since the last successful update to %s", machine.Name)
 | 
										Msgf("There has been updates since the last successful update to %s", machine.Name)
 | 
				
			||||||
				data, err := h.getMapResponse(machineKey, mapRequest, machine)
 | 
									data, err := h.getMapResponse(machineKey, mapRequest, machine)
 | 
				
			||||||
@ -464,10 +492,14 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
						Msg("Cannot update machine LastSuccessfulUpdate")
 | 
											Msg("Cannot update machine LastSuccessfulUpdate")
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
 | 
									var lastUpdate time.Time
 | 
				
			||||||
 | 
									if machine.LastSuccessfulUpdate != nil {
 | 
				
			||||||
 | 
										lastUpdate = *machine.LastSuccessfulUpdate
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				log.Trace().
 | 
									log.Trace().
 | 
				
			||||||
					Str("handler", "PollNetMapStream").
 | 
										Str("handler", "PollNetMapStream").
 | 
				
			||||||
					Str("machine", machine.Name).
 | 
										Str("machine", machine.Name).
 | 
				
			||||||
					Time("last_successful_update", *machine.LastSuccessfulUpdate).
 | 
										Time("last_successful_update", lastUpdate).
 | 
				
			||||||
					Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
 | 
										Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
 | 
				
			||||||
					Msgf("%s is up to date", machine.Name)
 | 
										Msgf("%s is up to date", machine.Name)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -507,42 +539,13 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
					Msg("Cannot update machine LastSeen")
 | 
										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
 | 
								return false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) scheduledPollWorker(
 | 
					func (h *Headscale) scheduledPollWorker(
 | 
				
			||||||
	cancelChan <-chan struct{},
 | 
						ctx context.Context,
 | 
				
			||||||
	updateChan chan<- struct{},
 | 
						updateChan chan<- struct{},
 | 
				
			||||||
	keepAliveChan chan<- []byte,
 | 
						keepAliveChan chan<- []byte,
 | 
				
			||||||
	machineKey key.MachinePublic,
 | 
						machineKey key.MachinePublic,
 | 
				
			||||||
@ -554,7 +557,7 @@ func (h *Headscale) scheduledPollWorker(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-cancelChan:
 | 
							case <-ctx.Done():
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case <-keepAliveTicker.C:
 | 
							case <-keepAliveTicker.C:
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ message Machine {
 | 
				
			|||||||
    string machine_key  = 2;
 | 
					    string machine_key  = 2;
 | 
				
			||||||
    string node_key     = 3;
 | 
					    string node_key     = 3;
 | 
				
			||||||
    string disco_key    = 4;
 | 
					    string disco_key    = 4;
 | 
				
			||||||
    string ip_address   = 5;
 | 
					    repeated string ip_addresses   = 5;
 | 
				
			||||||
    string name         = 6;
 | 
					    string name         = 6;
 | 
				
			||||||
    Namespace namespace = 7;
 | 
					    Namespace namespace = 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ package headscale
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
 | 
						"inet.af/netaddr"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateNodeNamespace(
 | 
					func CreateNodeNamespace(
 | 
				
			||||||
@ -26,7 +27,7 @@ func CreateNodeNamespace(
 | 
				
			|||||||
		NamespaceID:    namespace.ID,
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      ip,
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
		AuthKeyID:      uint(pak1.ID),
 | 
							AuthKeyID:      uint(pak1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machine)
 | 
						app.db.Save(machine)
 | 
				
			||||||
@ -214,7 +215,7 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			|||||||
		NamespaceID:    namespace1.ID,
 | 
							NamespaceID:    namespace1.ID,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.4",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 | 
				
			||||||
		AuthKeyID:      uint(pak4.ID),
 | 
							AuthKeyID:      uint(pak4.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machine4)
 | 
						app.db.Save(machine4)
 | 
				
			||||||
@ -294,7 +295,7 @@ func (s *Suite) TestDeleteSharedMachine(c *check.C) {
 | 
				
			|||||||
		NamespaceID:    namespace1.ID,
 | 
							NamespaceID:    namespace1.ID,
 | 
				
			||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		IPAddress:      "100.64.0.4",
 | 
							IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 | 
				
			||||||
		AuthKeyID:      uint(pak4n1.ID),
 | 
							AuthKeyID:      uint(pak4n1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(machine4)
 | 
						app.db.Save(machine4)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										77
									
								
								utils.go
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								utils.go
									
									
									
									
									
								
							@ -133,61 +133,78 @@ func encode(
 | 
				
			|||||||
	return privKey.SealTo(*pubKey, b), nil
 | 
						return privKey.SealTo(*pubKey, b), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getAvailableIP() (*netaddr.IP, error) {
 | 
					func (h *Headscale) getAvailableIPs() (ips MachineAddresses, err error) {
 | 
				
			||||||
	ipPrefix := h.cfg.IPPrefix
 | 
						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()
 | 
						usedIps, err := h.getUsedIPs()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ipPrefixNetworkAddress, ipPrefixBroadcastAddress := GetIPPrefixEndpoints(ipPrefix)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get the first IP in our prefix
 | 
						// Get the first IP in our prefix
 | 
				
			||||||
	ip := ipPrefix.IP()
 | 
						ip := ipPrefixNetworkAddress.Next()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		if !ipPrefix.Contains(ip) {
 | 
							if !ipPrefix.Contains(ip) {
 | 
				
			||||||
			return nil, errCouldNotAllocateIP
 | 
								return nil, errCouldNotAllocateIP
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Some OS (including Linux) does not like when IPs ends with 0 or 255, which
 | 
							switch {
 | 
				
			||||||
		// is typically called network or broadcast. Lets avoid them and continue
 | 
							case ip.Compare(ipPrefixBroadcastAddress) == 0:
 | 
				
			||||||
		// to look when we get one of those traditionally reserved IPs.
 | 
								fallthrough
 | 
				
			||||||
		ipRaw := ip.As4()
 | 
							case containsIPs(usedIps, ip):
 | 
				
			||||||
		if ipRaw[3] == 0 || ipRaw[3] == 255 {
 | 
								fallthrough
 | 
				
			||||||
 | 
							case ip.IsZero() || ip.IsLoopback():
 | 
				
			||||||
			ip = ip.Next()
 | 
								ip = ip.Next()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if ip.IsZero() &&
 | 
							default:
 | 
				
			||||||
			ip.IsLoopback() {
 | 
					 | 
				
			||||||
			ip = ip.Next()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if !containsIPs(usedIps, ip) {
 | 
					 | 
				
			||||||
			return &ip, nil
 | 
								return &ip, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		ip = ip.Next()
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) {
 | 
					func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) {
 | 
				
			||||||
	var addresses []string
 | 
						// FIXME: This really deserves a better data model,
 | 
				
			||||||
	h.db.Model(&Machine{}).Pluck("ip_address", &addresses)
 | 
						// 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))
 | 
						ips := make([]netaddr.IP, 0, len(h.cfg.IPPrefixes)*len(addressesSlices))
 | 
				
			||||||
	for index, addr := range addresses {
 | 
						for _, slice := range addressesSlices {
 | 
				
			||||||
		if addr != "" {
 | 
							var a MachineAddresses
 | 
				
			||||||
			ip, err := netaddr.ParseIP(addr)
 | 
							err := a.Scan(slice)
 | 
				
			||||||
			if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
				return nil, fmt.Errorf("failed to parse ip from database: %w", err)
 | 
								return nil, fmt.Errorf("failed to read ip from database: %w", err)
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			ips[index] = ip
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							ips = append(ips, a...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ips, nil
 | 
						return ips, nil
 | 
				
			||||||
 | 
				
			|||||||
@ -6,17 +6,18 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestGetAvailableIp(c *check.C) {
 | 
					func (s *Suite) TestGetAvailableIp(c *check.C) {
 | 
				
			||||||
	ip, err := app.getAvailableIP()
 | 
						ips, err := app.getAvailableIPs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expected := netaddr.MustParseIP("10.27.0.1")
 | 
						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) {
 | 
					func (s *Suite) TestGetUsedIps(c *check.C) {
 | 
				
			||||||
	ip, err := app.getAvailableIP()
 | 
						ips, err := app.getAvailableIPs()
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	namespace, err := app.CreateNamespace("test_ip")
 | 
						namespace, err := app.CreateNamespace("test_ip")
 | 
				
			||||||
@ -38,22 +39,24 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
 | 
				
			|||||||
		Registered:     true,
 | 
							Registered:     true,
 | 
				
			||||||
		RegisterMethod: RegisterMethodAuthKey,
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
		AuthKeyID:      uint(pak.ID),
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
		IPAddress:      ip.String(),
 | 
							IPAddresses:    ips,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(&machine)
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ips, err := app.getUsedIPs()
 | 
						usedIps, err := app.getUsedIPs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expected := netaddr.MustParseIP("10.27.0.1")
 | 
						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)
 | 
						machine1, err := app.GetMachineByID(0)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						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) {
 | 
					func (s *Suite) TestGetMultiIp(c *check.C) {
 | 
				
			||||||
@ -61,7 +64,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
 | 
				
			|||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index := 1; index <= 350; index++ {
 | 
						for index := 1; index <= 350; index++ {
 | 
				
			||||||
		ip, err := app.getAvailableIP()
 | 
							ips, err := app.getAvailableIPs()
 | 
				
			||||||
		c.Assert(err, check.IsNil)
 | 
							c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
							pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
				
			||||||
@ -80,59 +83,64 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
 | 
				
			|||||||
			Registered:     true,
 | 
								Registered:     true,
 | 
				
			||||||
			RegisterMethod: RegisterMethodAuthKey,
 | 
								RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
			AuthKeyID:      uint(pak.ID),
 | 
								AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
			IPAddress:      ip.String(),
 | 
								IPAddresses:    ips,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		app.db.Save(&machine)
 | 
							app.db.Save(&machine)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ips, err := app.getUsedIPs()
 | 
						usedIps, err := app.getUsedIPs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						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(usedIps[0], check.Equals, netaddr.MustParseIP("10.27.0.1"))
 | 
				
			||||||
	c.Assert(ips[9], check.Equals, netaddr.MustParseIP("10.27.0.10"))
 | 
						c.Assert(usedIps[9], check.Equals, netaddr.MustParseIP("10.27.0.10"))
 | 
				
			||||||
	c.Assert(ips[300], check.Equals, netaddr.MustParseIP("10.27.1.47"))
 | 
						c.Assert(usedIps[300], check.Equals, netaddr.MustParseIP("10.27.1.45"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check that we can read back the IPs
 | 
						// Check that we can read back the IPs
 | 
				
			||||||
	machine1, err := app.GetMachineByID(1)
 | 
						machine1, err := app.GetMachineByID(1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(len(machine1.IPAddresses), check.Equals, 1)
 | 
				
			||||||
	c.Assert(
 | 
						c.Assert(
 | 
				
			||||||
		machine1.IPAddress,
 | 
							machine1.IPAddresses[0],
 | 
				
			||||||
		check.Equals,
 | 
							check.Equals,
 | 
				
			||||||
		netaddr.MustParseIP("10.27.0.1").String(),
 | 
							netaddr.MustParseIP("10.27.0.1"),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machine50, err := app.GetMachineByID(50)
 | 
						machine50, err := app.GetMachineByID(50)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(len(machine50.IPAddresses), check.Equals, 1)
 | 
				
			||||||
	c.Assert(
 | 
						c.Assert(
 | 
				
			||||||
		machine50.IPAddress,
 | 
							machine50.IPAddresses[0],
 | 
				
			||||||
		check.Equals,
 | 
							check.Equals,
 | 
				
			||||||
		netaddr.MustParseIP("10.27.0.50").String(),
 | 
							netaddr.MustParseIP("10.27.0.50"),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expectedNextIP := netaddr.MustParseIP("10.27.1.97")
 | 
						expectedNextIP := netaddr.MustParseIP("10.27.1.95")
 | 
				
			||||||
	nextIP, err := app.getAvailableIP()
 | 
						nextIP, err := app.getAvailableIPs()
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						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
 | 
						// If we call get Available again, we should receive
 | 
				
			||||||
	// the same IP, as it has not been reserved.
 | 
						// the same IP, as it has not been reserved.
 | 
				
			||||||
	nextIP2, err := app.getAvailableIP()
 | 
						nextIP2, err := app.getAvailableIPs()
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						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) {
 | 
					func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
 | 
				
			||||||
	ip, err := app.getAvailableIP()
 | 
						ips, err := app.getAvailableIPs()
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expected := netaddr.MustParseIP("10.27.0.1")
 | 
						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")
 | 
						namespace, err := app.CreateNamespace("test_ip")
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
@ -156,8 +164,9 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	app.db.Save(&machine)
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ip2, err := app.getAvailableIP()
 | 
						ips2, err := app.getAvailableIPs()
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						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())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user