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: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
| 
 | ||||
|       - name: Get changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v14.1 | ||||
|         with: | ||||
|           files: | | ||||
|             go.* | ||||
|             **/*.go | ||||
|             integration_test/ | ||||
|             config-example.yaml | ||||
| 
 | ||||
|       - name: Setup Go | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         uses: actions/setup-go@v2 | ||||
|         with: | ||||
|           go-version: "1.17" | ||||
| 
 | ||||
|       - name: Install dependencies | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         run: | | ||||
|           go version | ||||
|           sudo apt update | ||||
|           sudo apt install -y make | ||||
| 
 | ||||
|       - name: Run build | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         run: make build | ||||
| 
 | ||||
|       - uses: actions/upload-artifact@v2 | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         with: | ||||
|           name: headscale-linux | ||||
|           path: headscale | ||||
|  | ||||
							
								
								
									
										31
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							| @ -8,8 +8,21 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
| 
 | ||||
|       - name: Get changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v14.1 | ||||
|         with: | ||||
|           files: | | ||||
|             go.* | ||||
|             **/*.go | ||||
|             integration_test/ | ||||
|             config-example.yaml | ||||
| 
 | ||||
|       - name: golangci-lint | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         uses: golangci/golangci-lint-action@v2 | ||||
|         with: | ||||
|           version: latest | ||||
| @ -24,8 +37,26 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
| 
 | ||||
|       - name: Get changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v14.1 | ||||
|         with: | ||||
|           files: | | ||||
|             **/*.md | ||||
|             **/*.yml | ||||
|             **/*.yaml | ||||
|             **/*.ts | ||||
|             **/*.js | ||||
|             **/*.sass | ||||
|             **/*.css | ||||
|             **/*.scss | ||||
|             **/*.html | ||||
| 
 | ||||
|       - name: Prettify code | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         uses: creyD/prettier_action@v4.0 | ||||
|         with: | ||||
|           prettier_options: >- | ||||
|  | ||||
							
								
								
									
										19
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,21 +3,30 @@ name: CI | ||||
| on: [pull_request] | ||||
| 
 | ||||
| jobs: | ||||
|   # The "build" workflow | ||||
|   integration-test: | ||||
|     # The type of runner that the job will run on | ||||
|     runs-on: ubuntu-latest | ||||
| 
 | ||||
|     # Steps represent a sequence of tasks that will be executed as part of the job | ||||
|     steps: | ||||
|       # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
| 
 | ||||
|       - name: Get changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v14.1 | ||||
|         with: | ||||
|           files: | | ||||
|             go.* | ||||
|             **/*.go | ||||
|             integration_test/ | ||||
|             config-example.yaml | ||||
| 
 | ||||
|       # Setup Go | ||||
|       - name: Setup Go | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         uses: actions/setup-go@v2 | ||||
|         with: | ||||
|           go-version: "1.17" | ||||
| 
 | ||||
|       - name: Run Integration tests | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         run: go test -tags integration -timeout 30m | ||||
|  | ||||
							
								
								
									
										24
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,31 +3,41 @@ name: CI | ||||
| on: [push, pull_request] | ||||
| 
 | ||||
| jobs: | ||||
|   # The "build" workflow | ||||
|   test: | ||||
|     # The type of runner that the job will run on | ||||
|     runs-on: ubuntu-latest | ||||
| 
 | ||||
|     # Steps represent a sequence of tasks that will be executed as part of the job | ||||
|     steps: | ||||
|       # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
| 
 | ||||
|       - name: Get changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v14.1 | ||||
|         with: | ||||
|           files: | | ||||
|             go.* | ||||
|             **/*.go | ||||
|             integration_test/ | ||||
|             config-example.yaml | ||||
| 
 | ||||
|       # Setup Go | ||||
|       - name: Setup Go | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         uses: actions/setup-go@v2 | ||||
|         with: | ||||
|           go-version: "1.17" # The Go version to download (if necessary) and use. | ||||
|           go-version: "1.17" | ||||
| 
 | ||||
|       # Install all the dependencies | ||||
|       - name: Install dependencies | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         run: | | ||||
|           go version | ||||
|           sudo apt update | ||||
|           sudo apt install -y make | ||||
| 
 | ||||
|       - name: Run tests | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         run: make test | ||||
| 
 | ||||
|       - name: Run build | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         run: make | ||||
|  | ||||
| @ -1,8 +1,7 @@ | ||||
| # This is an example .goreleaser.yml file with some sane defaults. | ||||
| # Make sure to check the documentation at http://goreleaser.com | ||||
| --- | ||||
| before: | ||||
|   hooks: | ||||
|     - go mod tidy | ||||
|     - go mod tidy -compat=1.17 | ||||
| 
 | ||||
| release: | ||||
|   prerelease: auto | ||||
| @ -33,7 +32,7 @@ builds: | ||||
|     goarch: | ||||
|       - arm | ||||
|     goarm: | ||||
|       - 7 | ||||
|       - "7" | ||||
|     env: | ||||
|       - CC=arm-linux-gnueabihf-gcc | ||||
|       - CXX=arm-linux-gnueabihf-g++ | ||||
|  | ||||
							
								
								
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -2,6 +2,18 @@ | ||||
| 
 | ||||
| **TBD (TBD):** | ||||
| 
 | ||||
| **0.13.0 (2022-xx-xx):** | ||||
| 
 | ||||
| **Features**: | ||||
| 
 | ||||
| - Add IPv6 support to the prefix assigned to namespaces | ||||
| 
 | ||||
| **Changes**: | ||||
| 
 | ||||
| - `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208) | ||||
| 
 | ||||
| **0.12.4 (2022-01-29):** | ||||
| 
 | ||||
| **Changes**: | ||||
| 
 | ||||
| - Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292) | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| # Builder image | ||||
| FROM golang:1.17.6-bullseye AS build | ||||
| FROM docker.io/golang:1.17.1-bullseye AS build | ||||
| ENV GOPATH /go | ||||
| WORKDIR /go/src/headscale | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| # Builder image | ||||
| FROM golang:1.17.6-alpine AS build | ||||
| FROM docker.io/golang:1.17.1-alpine AS build | ||||
| ENV GOPATH /go | ||||
| WORKDIR /go/src/headscale | ||||
| 
 | ||||
| @ -14,7 +14,7 @@ RUN strip /go/bin/headscale | ||||
| RUN test -e /go/bin/headscale | ||||
| 
 | ||||
| # Production image | ||||
| FROM alpine:latest | ||||
| FROM docker.io/alpine:latest | ||||
| 
 | ||||
| COPY --from=build /go/bin/headscale /bin/headscale | ||||
| ENV TZ UTC | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| # Builder image | ||||
| FROM golang:1.17.1-bullseye AS build | ||||
| FROM docker.io/golang:1.17.1-bullseye AS build | ||||
| ENV GOPATH /go | ||||
| WORKDIR /go/src/headscale | ||||
| 
 | ||||
|  | ||||
| @ -7,5 +7,5 @@ RUN apt-get update \ | ||||
|     && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | apt-key add - \ | ||||
|     && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \ | ||||
|     && apt-get update \ | ||||
|     && apt-get install -y tailscale=${TAILSCALE_VERSION} \ | ||||
|     && apt-get install -y tailscale=${TAILSCALE_VERSION} dnsutils \ | ||||
|     && rm -rf /var/lib/apt/lists/* | ||||
|  | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -18,7 +18,7 @@ test: | ||||
| 	@go test -coverprofile=coverage.out ./... | ||||
| 
 | ||||
| test_integration: | ||||
| 	go test -tags integration -timeout 30m ./... | ||||
| 	go test -tags integration -timeout 30m -count=1 ./... | ||||
| 
 | ||||
| test_integration_cli: | ||||
| 	go test -tags integration -v integration_cli_test.go integration_common_test.go | ||||
|  | ||||
							
								
								
									
										6
									
								
								acls.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								acls.go
									
									
									
									
									
								
							| @ -188,7 +188,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) { | ||||
| 				return nil, errInvalidNamespace | ||||
| 			} | ||||
| 			for _, node := range nodes { | ||||
| 				ips = append(ips, node.IPAddress) | ||||
| 				ips = append(ips, node.IPAddresses.ToStringSlice()...) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| @ -222,7 +222,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) { | ||||
| 				// FIXME: Check TagOwners allows this
 | ||||
| 				for _, t := range hostinfo.RequestTags { | ||||
| 					if alias[4:] == t { | ||||
| 						ips = append(ips, machine.IPAddress) | ||||
| 						ips = append(ips, machine.IPAddresses.ToStringSlice()...) | ||||
| 
 | ||||
| 						break | ||||
| 					} | ||||
| @ -241,7 +241,7 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) { | ||||
| 		} | ||||
| 		ips := []string{} | ||||
| 		for _, n := range nodes { | ||||
| 			ips = append(ips, n.IPAddress) | ||||
| 			ips = append(ips, n.IPAddresses.ToStringSlice()...) | ||||
| 		} | ||||
| 
 | ||||
| 		return ips, nil | ||||
|  | ||||
							
								
								
									
										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.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(5400)) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500)) | ||||
| 	c.Assert(rules[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(5400)) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500)) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestPortWildcard(c *check.C) { | ||||
| @ -75,11 +75,11 @@ func (s *Suite) TestPortWildcard(c *check.C) { | ||||
| 	c.Assert(rules, check.NotNil) | ||||
| 
 | ||||
| 	c.Assert(rules, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) | ||||
| 	c.Assert((rules)[0].SrcIPs, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].SrcIPs[0], check.Equals, "*") | ||||
| 	c.Assert(rules[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) | ||||
| 	c.Assert(rules[0].SrcIPs, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].SrcIPs[0], check.Equals, "*") | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| @ -91,7 +91,7 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 
 | ||||
| 	_, err = app.GetMachine("testnamespace", "testmachine") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 	ip, _ := app.getAvailableIP() | ||||
| 	ips, _ := app.getAvailableIPs() | ||||
| 	machine := Machine{ | ||||
| 		ID:             0, | ||||
| 		MachineKey:     "foo", | ||||
| @ -101,7 +101,7 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      ip.String(), | ||||
| 		IPAddresses:    ips, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| @ -116,12 +116,13 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 	c.Assert(rules, check.NotNil) | ||||
| 
 | ||||
| 	c.Assert(rules, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) | ||||
| 	c.Assert((rules)[0].SrcIPs, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip") | ||||
| 	c.Assert((rules)[0].SrcIPs[0], check.Equals, ip.String()) | ||||
| 	c.Assert(rules[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) | ||||
| 	c.Assert(rules[0].SrcIPs, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].SrcIPs[0], check.Not(check.Equals), "not an ip") | ||||
| 	c.Assert(len(ips), check.Equals, 1) | ||||
| 	c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String()) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestPortGroup(c *check.C) { | ||||
| @ -133,7 +134,7 @@ func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 
 | ||||
| 	_, err = app.GetMachine("testnamespace", "testmachine") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 	ip, _ := app.getAvailableIP() | ||||
| 	ips, _ := app.getAvailableIPs() | ||||
| 	machine := Machine{ | ||||
| 		ID:             0, | ||||
| 		MachineKey:     "foo", | ||||
| @ -143,7 +144,7 @@ func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      ip.String(), | ||||
| 		IPAddresses:    ips, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| @ -156,10 +157,11 @@ func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 	c.Assert(rules, check.NotNil) | ||||
| 
 | ||||
| 	c.Assert(rules, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) | ||||
| 	c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) | ||||
| 	c.Assert((rules)[0].SrcIPs, check.HasLen, 1) | ||||
| 	c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip") | ||||
| 	c.Assert((rules)[0].SrcIPs[0], check.Equals, ip.String()) | ||||
| 	c.Assert(rules[0].DstPorts, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) | ||||
| 	c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) | ||||
| 	c.Assert(rules[0].SrcIPs, check.HasLen, 1) | ||||
| 	c.Assert(rules[0].SrcIPs[0], check.Not(check.Equals), "not an ip") | ||||
| 	c.Assert(len(ips), check.Equals, 1) | ||||
| 	c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String()) | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								api.go
									
									
									
									
									
								
							| @ -497,6 +497,7 @@ func (h *Headscale) handleMachineRegistrationNew( | ||||
| 	ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) | ||||
| } | ||||
| 
 | ||||
| // TODO: check if any locks are needed around IP allocation.
 | ||||
| func (h *Headscale) handleAuthKey( | ||||
| 	ctx *gin.Context, | ||||
| 	machineKey key.MachinePublic, | ||||
| @ -554,14 +555,14 @@ func (h *Headscale) handleAuthKey( | ||||
| 		log.Debug(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("Authentication key was valid, proceeding to acquire an IP address") | ||||
| 		ip, err := h.getAvailableIP() | ||||
| 			Msg("Authentication key was valid, proceeding to acquire IP addresses") | ||||
| 		ips, err := h.getAvailableIPs() | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| 				Str("func", "handleAuthKey"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Msg("Failed to find an available IP") | ||||
| 				Msg("Failed to find an available IP address") | ||||
| 			machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name). | ||||
| 				Inc() | ||||
| 
 | ||||
| @ -570,12 +571,12 @@ func (h *Headscale) handleAuthKey( | ||||
| 		log.Info(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Str("ip", ip.String()). | ||||
| 			Msgf("Assigning %s to %s", ip, machine.Name) | ||||
| 			Str("ips", strings.Join(ips.ToStringSlice(), ",")). | ||||
| 			Msgf("Assigning %s to %s", strings.Join(ips.ToStringSlice(), ","), machine.Name) | ||||
| 
 | ||||
| 		machine.Expiry = ®isterRequest.Expiry | ||||
| 		machine.AuthKeyID = uint(pak.ID) | ||||
| 		machine.IPAddress = ip.String() | ||||
| 		machine.IPAddresses = ips | ||||
| 		machine.NamespaceID = pak.NamespaceID | ||||
| 
 | ||||
| 		machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey) | ||||
| @ -610,6 +611,6 @@ func (h *Headscale) handleAuthKey( | ||||
| 	log.Info(). | ||||
| 		Str("func", "handleAuthKey"). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Str("ip", machine.IPAddress). | ||||
| 		Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")). | ||||
| 		Msg("Successfully authenticated via AuthKey") | ||||
| } | ||||
|  | ||||
							
								
								
									
										6
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								app.go
									
									
									
									
									
								
							| @ -73,7 +73,7 @@ type Config struct { | ||||
| 	ServerURL                      string | ||||
| 	Addr                           string | ||||
| 	EphemeralNodeInactivityTimeout time.Duration | ||||
| 	IPPrefix                       netaddr.IPPrefix | ||||
| 	IPPrefixes                     []netaddr.IPPrefix | ||||
| 	PrivateKeyPath                 string | ||||
| 	BaseDomain                     string | ||||
| 
 | ||||
| @ -204,9 +204,7 @@ func NewHeadscale(cfg Config) (*Headscale, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
 | ||||
| 		magicDNSDomains := generateMagicDNSRootDomains( | ||||
| 			app.cfg.IPPrefix, | ||||
| 		) | ||||
| 		magicDNSDomains := generateMagicDNSRootDomains(app.cfg.IPPrefixes) | ||||
| 		// we might have routes already from Split DNS
 | ||||
| 		if app.cfg.DNSConfig.Routes == nil { | ||||
| 			app.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) | ||||
|  | ||||
| @ -41,7 +41,9 @@ func (s *Suite) ResetDB(c *check.C) { | ||||
| 		c.Fatal(err) | ||||
| 	} | ||||
| 	cfg := Config{ | ||||
| 		IPPrefix: netaddr.MustParseIPPrefix("10.27.0.0/23"), | ||||
| 		IPPrefixes: []netaddr.IPPrefix{ | ||||
| 			netaddr.MustParseIPPrefix("10.27.0.0/23"), | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	app = Headscale{ | ||||
|  | ||||
							
								
								
									
										10
									
								
								cli_test.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								cli_test.go
									
									
									
									
									
								
							| @ -4,6 +4,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"gopkg.in/check.v1" | ||||
| 	"inet.af/netaddr" | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestRegisterMachine(c *check.C) { | ||||
| @ -19,16 +20,17 @@ func (s *Suite) TestRegisterMachine(c *check.C) { | ||||
| 		DiscoKey:    "faa", | ||||
| 		Name:        "testmachine", | ||||
| 		NamespaceID: namespace.ID, | ||||
| 		IPAddress:   "10.0.0.1", | ||||
| 		IPAddresses: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")}, | ||||
| 		Expiry:      &now, | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| 	err = app.db.Save(&machine).Error | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "testmachine") | ||||
| 	_, err = app.GetMachine(namespace.Name, machine.Name) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineAfterRegistering, err := app.RegisterMachine( | ||||
| 		"8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e", | ||||
| 		machine.MachineKey, | ||||
| 		namespace.Name, | ||||
| 	) | ||||
| 	c.Assert(err, check.IsNil) | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	survey "github.com/AlecAivazis/survey/v2" | ||||
| @ -459,7 +460,7 @@ func nodesToPtables( | ||||
| 			"Name", | ||||
| 			"NodeKey", | ||||
| 			"Namespace", | ||||
| 			"IP address", | ||||
| 			"IP addresses", | ||||
| 			"Ephemeral", | ||||
| 			"Last seen", | ||||
| 			"Online", | ||||
| @ -523,7 +524,7 @@ func nodesToPtables( | ||||
| 				machine.Name, | ||||
| 				nodeKey.ShortString(), | ||||
| 				namespace, | ||||
| 				machine.IpAddress, | ||||
| 				strings.Join(machine.IpAddresses, ", "), | ||||
| 				strconv.FormatBool(ephemeral), | ||||
| 				lastSeenTime, | ||||
| 				online, | ||||
|  | ||||
| @ -48,8 +48,6 @@ func LoadConfig(path string) error { | ||||
| 	viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01") | ||||
| 	viper.SetDefault("tls_client_auth_mode", "relaxed") | ||||
| 
 | ||||
| 	viper.SetDefault("ip_prefix", "100.64.0.0/10") | ||||
| 
 | ||||
| 	viper.SetDefault("log_level", "info") | ||||
| 
 | ||||
| 	viper.SetDefault("dns_config", nil) | ||||
| @ -235,10 +233,57 @@ func getHeadscaleConfig() headscale.Config { | ||||
| 	dnsConfig, baseDomain := GetDNSConfig() | ||||
| 	derpConfig := GetDERPConfig() | ||||
| 
 | ||||
| 	configuredPrefixes := viper.GetStringSlice("ip_prefixes") | ||||
| 	parsedPrefixes := make([]netaddr.IPPrefix, 0, len(configuredPrefixes)+1) | ||||
| 
 | ||||
| 	legacyPrefixField := viper.GetString("ip_prefix") | ||||
| 	if len(legacyPrefixField) > 0 { | ||||
| 		log. | ||||
| 			Warn(). | ||||
| 			Msgf( | ||||
| 				"%s, %s", | ||||
| 				"use of 'ip_prefix' for configuration is deprecated", | ||||
| 				"please see 'ip_prefixes' in the shipped example.", | ||||
| 			) | ||||
| 		legacyPrefix, err := netaddr.ParseIPPrefix(legacyPrefixField) | ||||
| 		if err != nil { | ||||
| 			panic(fmt.Errorf("failed to parse ip_prefix: %w", err)) | ||||
| 		} | ||||
| 		parsedPrefixes = append(parsedPrefixes, legacyPrefix) | ||||
| 	} | ||||
| 
 | ||||
| 	for i, prefixInConfig := range configuredPrefixes { | ||||
| 		prefix, err := netaddr.ParseIPPrefix(prefixInConfig) | ||||
| 		if err != nil { | ||||
| 			panic(fmt.Errorf("failed to parse ip_prefixes[%d]: %w", i, err)) | ||||
| 		} | ||||
| 		parsedPrefixes = append(parsedPrefixes, prefix) | ||||
| 	} | ||||
| 
 | ||||
| 	prefixes := make([]netaddr.IPPrefix, 0, len(parsedPrefixes)) | ||||
| 	{ | ||||
| 		// dedup
 | ||||
| 		normalizedPrefixes := make(map[string]int, len(parsedPrefixes)) | ||||
| 		for i, p := range parsedPrefixes { | ||||
| 			normalized, _ := p.Range().Prefix() | ||||
| 			normalizedPrefixes[normalized.String()] = i | ||||
| 		} | ||||
| 
 | ||||
| 		// convert back to list
 | ||||
| 		for _, i := range normalizedPrefixes { | ||||
| 			prefixes = append(prefixes, parsedPrefixes[i]) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(prefixes) < 1 { | ||||
| 		prefixes = append(prefixes, netaddr.MustParseIPPrefix("100.64.0.0/10")) | ||||
| 		log.Warn().Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes) | ||||
| 	} | ||||
| 
 | ||||
| 	return headscale.Config{ | ||||
| 		ServerURL:      viper.GetString("server_url"), | ||||
| 		Addr:           viper.GetString("listen_addr"), | ||||
| 		IPPrefix:       netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")), | ||||
| 		IPPrefixes:     prefixes, | ||||
| 		PrivateKeyPath: absPath(viper.GetString("private_key_path")), | ||||
| 		BaseDomain:     baseDomain, | ||||
| 
 | ||||
|  | ||||
| @ -22,6 +22,13 @@ listen_addr: 0.0.0.0:8080 | ||||
| # autogenerated if it's missing | ||||
| private_key_path: /var/lib/headscale/private.key | ||||
| 
 | ||||
| # List of IP prefixes to allocate tailaddresses from. | ||||
| # Each prefix consists of either an IPv4 or IPv6 address, | ||||
| # and the associated prefix length, delimited by a slash. | ||||
| ip_prefixes: | ||||
|   - fd7a:115c:a1e0::/48 | ||||
|   - 100.64.0.0/10 | ||||
| 
 | ||||
| # DERP is a relay system that Tailscale uses when a direct | ||||
| # connection cannot be established. | ||||
| # https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp | ||||
|  | ||||
							
								
								
									
										8
									
								
								db.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								db.go
									
									
									
									
									
								
							| @ -28,20 +28,26 @@ func (h *Headscale) initDB() error { | ||||
| 	h.db = db | ||||
| 
 | ||||
| 	if h.dbType == Postgres { | ||||
| 		db.Exec("create extension if not exists \"uuid-ossp\";") | ||||
| 		db.Exec(`create extension if not exists "uuid-ossp";`) | ||||
| 	} | ||||
| 
 | ||||
| 	_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses") | ||||
| 
 | ||||
| 	err = db.AutoMigrate(&Machine{}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = db.AutoMigrate(&KV{}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = db.AutoMigrate(&Namespace{}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = db.AutoMigrate(&PreAuthKey{}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  | ||||
							
								
								
									
										82
									
								
								dns.go
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								dns.go
									
									
									
									
									
								
							| @ -14,6 +14,11 @@ const ( | ||||
| 	ByteSize = 8 | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	ipv4AddressLength = 32 | ||||
| 	ipv6AddressLength = 128 | ||||
| ) | ||||
| 
 | ||||
| // generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
 | ||||
| // This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS
 | ||||
| // server (listening in 100.100.100.100 udp/53) should be used for.
 | ||||
| @ -34,14 +39,28 @@ const ( | ||||
| 
 | ||||
| // From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
 | ||||
| // This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
 | ||||
| func generateMagicDNSRootDomains( | ||||
| 	ipPrefix netaddr.IPPrefix, | ||||
| ) []dnsname.FQDN { | ||||
| 	// TODO(juanfont): we are not handing out IPv6 addresses yet
 | ||||
| 	// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
 | ||||
| 	ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.") | ||||
| 	fqdns := []dnsname.FQDN{ipv6base} | ||||
| func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN { | ||||
| 	fqdns := make([]dnsname.FQDN, 0, len(ipPrefixes)) | ||||
| 	for _, ipPrefix := range ipPrefixes { | ||||
| 		var generateDNSRoot func(netaddr.IPPrefix) []dnsname.FQDN | ||||
| 		switch ipPrefix.IP().BitLen() { | ||||
| 		case ipv4AddressLength: | ||||
| 			generateDNSRoot = generateIPv4DNSRootDomain | ||||
| 
 | ||||
| 		case ipv6AddressLength: | ||||
| 			generateDNSRoot = generateIPv6DNSRootDomain | ||||
| 
 | ||||
| 		default: | ||||
| 			panic(fmt.Sprintf("unsupported IP version with address length %d", ipPrefix.IP().BitLen())) | ||||
| 		} | ||||
| 
 | ||||
| 		fqdns = append(fqdns, generateDNSRoot(ipPrefix)...) | ||||
| 	} | ||||
| 
 | ||||
| 	return fqdns | ||||
| } | ||||
| 
 | ||||
| func generateIPv4DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN { | ||||
| 	// Conversion to the std lib net.IPnet, a bit easier to operate
 | ||||
| 	netRange := ipPrefix.IPNet() | ||||
| 	maskBits, _ := netRange.Mask.Size() | ||||
| @ -65,6 +84,7 @@ func generateMagicDNSRootDomains( | ||||
| 	rdnsSlice = append(rdnsSlice, "in-addr.arpa.") | ||||
| 	rdnsBase := strings.Join(rdnsSlice, ".") | ||||
| 
 | ||||
| 	fqdns := make([]dnsname.FQDN, 0, max-min+1) | ||||
| 	for i := min; i <= max; i++ { | ||||
| 		fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%d.%s", i, rdnsBase)) | ||||
| 		if err != nil { | ||||
| @ -76,6 +96,54 @@ func generateMagicDNSRootDomains( | ||||
| 	return fqdns | ||||
| } | ||||
| 
 | ||||
| func generateIPv6DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN { | ||||
| 	const nibbleLen = 4 | ||||
| 
 | ||||
| 	maskBits, _ := ipPrefix.IPNet().Mask.Size() | ||||
| 	expanded := ipPrefix.IP().StringExpanded() | ||||
| 	nibbleStr := strings.Map(func(r rune) rune { | ||||
| 		if r == ':' { | ||||
| 			return -1 | ||||
| 		} | ||||
| 
 | ||||
| 		return r | ||||
| 	}, expanded) | ||||
| 
 | ||||
| 	// TODO?: that does not look the most efficient implementation,
 | ||||
| 	// but the inputs are not so long as to cause problems,
 | ||||
| 	// and from what I can see, the generateMagicDNSRootDomains
 | ||||
| 	// function is called only once over the lifetime of a server process.
 | ||||
| 	prefixConstantParts := []string{} | ||||
| 	for i := 0; i < maskBits/nibbleLen; i++ { | ||||
| 		prefixConstantParts = append([]string{string(nibbleStr[i])}, prefixConstantParts...) | ||||
| 	} | ||||
| 
 | ||||
| 	makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) { | ||||
| 		prefix := strings.Join(append(variablePrefix, prefixConstantParts...), ".") | ||||
| 
 | ||||
| 		return dnsname.ToFQDN(fmt.Sprintf("%s.ip6.arpa", prefix)) | ||||
| 	} | ||||
| 
 | ||||
| 	var fqdns []dnsname.FQDN | ||||
| 	if maskBits%4 == 0 { | ||||
| 		dom, _ := makeDomain() | ||||
| 		fqdns = append(fqdns, dom) | ||||
| 	} else { | ||||
| 		domCount := 1 << (maskBits % nibbleLen) | ||||
| 		fqdns = make([]dnsname.FQDN, 0, domCount) | ||||
| 		for i := 0; i < domCount; i++ { | ||||
| 			varNibble := fmt.Sprintf("%x", i) | ||||
| 			dom, err := makeDomain(varNibble) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			fqdns = append(fqdns, dom) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return fqdns | ||||
| } | ||||
| 
 | ||||
| func getMapResponseDNSConfig( | ||||
| 	dnsConfigOrig *tailcfg.DNSConfig, | ||||
| 	baseDomain string, | ||||
|  | ||||
							
								
								
									
										62
									
								
								dns_test.go
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								dns_test.go
									
									
									
									
									
								
							| @ -10,8 +10,10 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestMagicDNSRootDomains100(c *check.C) { | ||||
| 	prefix := netaddr.MustParseIPPrefix("100.64.0.0/10") | ||||
| 	domains := generateMagicDNSRootDomains(prefix) | ||||
| 	prefixes := []netaddr.IPPrefix{ | ||||
| 		netaddr.MustParseIPPrefix("100.64.0.0/10"), | ||||
| 	} | ||||
| 	domains := generateMagicDNSRootDomains(prefixes) | ||||
| 
 | ||||
| 	found := false | ||||
| 	for _, domain := range domains { | ||||
| @ -45,8 +47,10 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestMagicDNSRootDomains172(c *check.C) { | ||||
| 	prefix := netaddr.MustParseIPPrefix("172.16.0.0/16") | ||||
| 	domains := generateMagicDNSRootDomains(prefix) | ||||
| 	prefixes := []netaddr.IPPrefix{ | ||||
| 		netaddr.MustParseIPPrefix("172.16.0.0/16"), | ||||
| 	} | ||||
| 	domains := generateMagicDNSRootDomains(prefixes) | ||||
| 
 | ||||
| 	found := false | ||||
| 	for _, domain := range domains { | ||||
| @ -69,6 +73,40 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) { | ||||
| 	c.Assert(found, check.Equals, true) | ||||
| } | ||||
| 
 | ||||
| // Happens when netmask is a multiple of 4 bits (sounds likely).
 | ||||
| func (s *Suite) TestMagicDNSRootDomainsIPv6Single(c *check.C) { | ||||
| 	prefixes := []netaddr.IPPrefix{ | ||||
| 		netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48"), | ||||
| 	} | ||||
| 	domains := generateMagicDNSRootDomains(prefixes) | ||||
| 
 | ||||
| 	c.Assert(len(domains), check.Equals, 1) | ||||
| 	c.Assert(domains[0].WithTrailingDot(), check.Equals, "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.") | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) { | ||||
| 	prefixes := []netaddr.IPPrefix{ | ||||
| 		netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/50"), | ||||
| 	} | ||||
| 	domains := generateMagicDNSRootDomains(prefixes) | ||||
| 
 | ||||
| 	yieldsRoot := func(dom string) bool { | ||||
| 		for _, candidate := range domains { | ||||
| 			if candidate.WithTrailingDot() == dom { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	c.Assert(len(domains), check.Equals, 4) | ||||
| 	c.Assert(yieldsRoot("0.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true) | ||||
| 	c.Assert(yieldsRoot("1.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true) | ||||
| 	c.Assert(yieldsRoot("2.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true) | ||||
| 	c.Assert(yieldsRoot("3.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 	namespaceShared1, err := app.CreateNamespace("shared1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| @ -124,7 +162,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.1", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared1) | ||||
| @ -142,7 +180,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.2", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared2.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared2) | ||||
| @ -160,7 +198,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.3", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared3.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared3) | ||||
| @ -178,7 +216,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.4", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(PreAuthKey2InShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine2InShared1) | ||||
| @ -273,7 +311,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.1", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared1) | ||||
| @ -291,7 +329,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.2", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared2.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared2) | ||||
| @ -309,7 +347,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.3", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared3.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared3) | ||||
| @ -327,7 +365,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.4", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(preAuthKey2InShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine2InShared1) | ||||
|  | ||||
| @ -44,7 +44,7 @@ touch /var/lib/headscale/db.sqlite | ||||
| touch /etc/headscale/config.yaml | ||||
| ``` | ||||
| 
 | ||||
| It is **strongly recommended** to copy and modifiy the [example configuration](../config-example.yaml) | ||||
| It is **strongly recommended** to copy and modify the [example configuration](../config-example.yaml) | ||||
| from the [headscale repository](../) | ||||
| 
 | ||||
| 6. Start the headscale server: | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.27.1
 | ||||
| // 	protoc        v3.18.1
 | ||||
| // 	protoc        v3.17.3
 | ||||
| // source: headscale/v1/device.proto
 | ||||
| 
 | ||||
| package v1 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.27.1
 | ||||
| // 	protoc        v3.18.1
 | ||||
| // 	protoc        v3.17.3
 | ||||
| // source: headscale/v1/headscale.proto
 | ||||
| 
 | ||||
| package v1 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.27.1
 | ||||
| // 	protoc        v3.18.1
 | ||||
| // 	protoc        v3.17.3
 | ||||
| // source: headscale/v1/machine.proto
 | ||||
| 
 | ||||
| package v1 | ||||
| @ -82,7 +82,7 @@ type Machine struct { | ||||
| 	MachineKey           string                 `protobuf:"bytes,2,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"` | ||||
| 	NodeKey              string                 `protobuf:"bytes,3,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` | ||||
| 	DiscoKey             string                 `protobuf:"bytes,4,opt,name=disco_key,json=discoKey,proto3" json:"disco_key,omitempty"` | ||||
| 	IpAddress            string                 `protobuf:"bytes,5,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"` | ||||
| 	IpAddresses          []string               `protobuf:"bytes,5,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` | ||||
| 	Name                 string                 `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` | ||||
| 	Namespace            *Namespace             `protobuf:"bytes,7,opt,name=namespace,proto3" json:"namespace,omitempty"` | ||||
| 	Registered           bool                   `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"` | ||||
| @ -154,11 +154,11 @@ func (x *Machine) GetDiscoKey() string { | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) GetIpAddress() string { | ||||
| func (x *Machine) GetIpAddresses() []string { | ||||
| 	if x != nil { | ||||
| 		return x.IpAddress | ||||
| 		return x.IpAddresses | ||||
| 	} | ||||
| 	return "" | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) GetName() string { | ||||
| @ -1026,129 +1026,129 @@ var file_headscale_v1_machine_proto_rawDesc = []byte{ | ||||
| 	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, | ||||
| 	0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x68, 0x65, 0x61, 0x64, 0x73, | ||||
| 	0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, | ||||
| 	0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf9, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63, | ||||
| 	0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63, | ||||
| 	0x68, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, | ||||
| 	0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, | ||||
| 	0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, | ||||
| 	0x6e, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, | ||||
| 	0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, | ||||
| 	0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, | ||||
| 	0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, | ||||
| 	0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, | ||||
| 	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, | ||||
| 	0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20, | ||||
| 	0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, | ||||
| 	0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61, | ||||
| 	0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, | ||||
| 	0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x67, | ||||
| 	0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73, | ||||
| 	0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, | ||||
| 	0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, | ||||
| 	0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, | ||||
| 	0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x37, | ||||
| 	0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, | ||||
| 	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, | ||||
| 	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c, | ||||
| 	0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f, | ||||
| 	0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, | ||||
| 	0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, | ||||
| 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, | ||||
| 	0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, | ||||
| 	0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70, | ||||
| 	0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, | ||||
| 	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, | ||||
| 	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x3a, 0x0a, | ||||
| 	0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0d, 0x20, | ||||
| 	0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, | ||||
| 	0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70, | ||||
| 	0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, | ||||
| 	0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, | ||||
| 	0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, | ||||
| 	0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20, | ||||
| 	0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, | ||||
| 	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, | ||||
| 	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, | ||||
| 	0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, | ||||
| 	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, | ||||
| 	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, | ||||
| 	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, | ||||
| 	0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, | ||||
| 	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, | ||||
| 	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, | ||||
| 	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, | ||||
| 	0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, | ||||
| 	0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, | ||||
| 	0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, | ||||
| 	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, | ||||
| 	0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, | ||||
| 	0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, | ||||
| 	0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, | ||||
| 	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, | ||||
| 	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, | ||||
| 	0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, | ||||
| 	0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, | ||||
| 	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, | ||||
| 	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, | ||||
| 	0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, | ||||
| 	0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, | ||||
| 	0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, | ||||
| 	0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4a, | ||||
| 	0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, | ||||
| 	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, | ||||
| 	0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, | ||||
| 	0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, | ||||
| 	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, | ||||
| 	0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, | ||||
| 	0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, | ||||
| 	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, | ||||
| 	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, | ||||
| 	0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, | ||||
| 	0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, | ||||
| 	0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, | ||||
| 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, | ||||
| 	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, | ||||
| 	0x65, 0x79, 0x22, 0x4a, 0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, | ||||
| 	0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, | ||||
| 	0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, | ||||
| 	0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, | ||||
| 	0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x32, | ||||
| 	0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, | ||||
| 	0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, | ||||
| 	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, | ||||
| 	0x49, 0x64, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, | ||||
| 	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, | ||||
| 	0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, | ||||
| 	0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, | ||||
| 	0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c, | ||||
| 	0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, | ||||
| 	0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, | ||||
| 	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, | ||||
| 	0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, | ||||
| 	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x45, 0x78, 0x70, | ||||
| 	0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, | ||||
| 	0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, | ||||
| 	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, | ||||
| 	0x22, 0x48, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, | ||||
| 	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, | ||||
| 	0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, | ||||
| 	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, | ||||
| 	0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65, | ||||
| 	0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, | ||||
| 	0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, | ||||
| 	0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x45, | ||||
| 	0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, | ||||
| 	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, | ||||
| 	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, | ||||
| 	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, | ||||
| 	0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, | ||||
| 	0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, | ||||
| 	0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||
| 	0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15, | ||||
| 	0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, | ||||
| 	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, | ||||
| 	0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, | ||||
| 	0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||
| 	0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x15, | ||||
| 	0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, | ||||
| 	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, | ||||
| 	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, | ||||
| 	0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, | ||||
| 	0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, | ||||
| 	0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, | ||||
| 	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, | ||||
| 	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, 0x14, 0x4c, | ||||
| 	0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, | ||||
| 	0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18, | ||||
| 	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, | ||||
| 	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x6d, 0x61, | ||||
| 	0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, | ||||
| 	0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, | ||||
| 	0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||
| 	0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, | ||||
| 	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, | ||||
| 	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47, 0x0a, 0x14, 0x53, 0x68, | ||||
| 	0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, | ||||
| 	0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, | ||||
| 	0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, | ||||
| 	0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, | ||||
| 	0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, | ||||
| 	0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, | ||||
| 	0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, | ||||
| 	0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, | ||||
| 	0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, | ||||
| 	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, 0x16, 0x55, 0x6e, 0x73, | ||||
| 	0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, | ||||
| 	0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, | ||||
| 	0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, | ||||
| 	0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, | ||||
| 	0x68, 0x69, 0x6e, 0x65, 0x22, 0x77, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, | ||||
| 	0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, | ||||
| 	0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69, | ||||
| 	0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, | ||||
| 	0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, | ||||
| 	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, | ||||
| 	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, | ||||
| 	0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, | ||||
| 	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, | ||||
| 	0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x4d, 0x0a, | ||||
| 	0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, | ||||
| 	0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, | ||||
| 	0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, | ||||
| 	0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, | ||||
| 	0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2a, 0x82, 0x01, 0x0a, | ||||
| 	0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, | ||||
| 	0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, | ||||
| 	0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, | ||||
| 	0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, | ||||
| 	0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17, | ||||
| 	0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, | ||||
| 	0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53, | ||||
| 	0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, | ||||
| 	0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, | ||||
| 	0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, | ||||
| 	0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, | ||||
| 	0x6f, 0x74, 0x6f, 0x33, | ||||
| 	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, | ||||
| 	0x49, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, | ||||
| 	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, | ||||
| 	0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, | ||||
| 	0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, | ||||
| 	0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68, | ||||
| 	0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, | ||||
| 	0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, | ||||
| 	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, | ||||
| 	0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, | ||||
| 	0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47, | ||||
| 	0x0a, 0x14, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, | ||||
| 	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, | ||||
| 	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, | ||||
| 	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, | ||||
| 	0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61, | ||||
| 	0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, | ||||
| 	0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, | ||||
| 	0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, | ||||
| 	0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, | ||||
| 	0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, | ||||
| 	0x16, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, | ||||
| 	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, | ||||
| 	0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, | ||||
| 	0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, | ||||
| 	0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x77, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x75, | ||||
| 	0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, | ||||
| 	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, | ||||
| 	0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, | ||||
| 	0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, | ||||
| 	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, | ||||
| 	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, | ||||
| 	0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, | ||||
| 	0x73, 0x22, 0x4d, 0x0a, 0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, | ||||
| 	0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, | ||||
| 	0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, | ||||
| 	0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, | ||||
| 	0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, | ||||
| 	0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, | ||||
| 	0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, | ||||
| 	0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, | ||||
| 	0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, | ||||
| 	0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, | ||||
| 	0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, | ||||
| 	0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, | ||||
| 	0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, | ||||
| 	0x49, 0x44, 0x43, 0x10, 0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, | ||||
| 	0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, | ||||
| 	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, | ||||
| 	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.27.1
 | ||||
| // 	protoc        v3.18.1
 | ||||
| // 	protoc        v3.17.3
 | ||||
| // source: headscale/v1/namespace.proto
 | ||||
| 
 | ||||
| package v1 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.27.1
 | ||||
| // 	protoc        v3.18.1
 | ||||
| // 	protoc        v3.17.3
 | ||||
| // source: headscale/v1/preauthkey.proto
 | ||||
| 
 | ||||
| package v1 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||
| // versions:
 | ||||
| // 	protoc-gen-go v1.27.1
 | ||||
| // 	protoc        v3.18.1
 | ||||
| // 	protoc        v3.17.3
 | ||||
| // source: headscale/v1/routes.proto
 | ||||
| 
 | ||||
| package v1 | ||||
|  | ||||
| @ -775,8 +775,11 @@ | ||||
|         "discoKey": { | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "ipAddress": { | ||||
|         "ipAddresses": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         }, | ||||
|         "name": { | ||||
|           "type": "string" | ||||
|  | ||||
| @ -8,22 +8,48 @@ import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"inet.af/netaddr" | ||||
| 
 | ||||
| 	"github.com/ory/dockertest/v3" | ||||
| 	"github.com/ory/dockertest/v3/docker" | ||||
| ) | ||||
| 
 | ||||
| const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second | ||||
| 
 | ||||
| var IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10") | ||||
| var IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48") | ||||
| 
 | ||||
| type ExecuteCommandConfig struct { | ||||
| 	timeout time.Duration | ||||
| } | ||||
| 
 | ||||
| type ExecuteCommandOption func(*ExecuteCommandConfig) error | ||||
| 
 | ||||
| func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption { | ||||
| 	return ExecuteCommandOption(func(conf *ExecuteCommandConfig) error { | ||||
| 		conf.timeout = timeout | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func ExecuteCommand( | ||||
| 	resource *dockertest.Resource, | ||||
| 	cmd []string, | ||||
| 	env []string, | ||||
| 	options ...ExecuteCommandOption, | ||||
| ) (string, error) { | ||||
| 	var stdout bytes.Buffer | ||||
| 	var stderr bytes.Buffer | ||||
| 
 | ||||
| 	// TODO(kradalby): Make configurable
 | ||||
| 	timeout := DOCKER_EXECUTE_TIMEOUT | ||||
| 	execConfig := ExecuteCommandConfig{ | ||||
| 		timeout: DOCKER_EXECUTE_TIMEOUT, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, opt := range options { | ||||
| 		if err := opt(&execConfig); err != nil { | ||||
| 			return "", fmt.Errorf("execute-command/options: %w", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	type result struct { | ||||
| 		exitCode int | ||||
| @ -62,16 +88,33 @@ func ExecuteCommand( | ||||
| 		} | ||||
| 
 | ||||
| 		return stdout.String(), nil | ||||
| 	case <-time.After(timeout): | ||||
| 	case <-time.After(execConfig.timeout): | ||||
| 
 | ||||
| 		return "", fmt.Errorf("command timed out after %s", timeout) | ||||
| 		return "", fmt.Errorf("command timed out after %s", execConfig.timeout) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func DockerRestartPolicy(config *docker.HostConfig) { | ||||
| 	// set AutoRemove to true so that stopped container goes away by itself
 | ||||
| 	config.AutoRemove = true | ||||
| 	// set AutoRemove to true so that stopped container goes away by itself on error *immediately*.
 | ||||
| 	// when set to false, containers remain until the end of the integration test.
 | ||||
| 	config.AutoRemove = false | ||||
| 	config.RestartPolicy = docker.RestartPolicy{ | ||||
| 		Name: "no", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func DockerAllowLocalIPv6(config *docker.HostConfig) { | ||||
| 	if config.Sysctls == nil { | ||||
| 		config.Sysctls = make(map[string]string, 1) | ||||
| 	} | ||||
| 	config.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0" | ||||
| } | ||||
| 
 | ||||
| func DockerAllowNetworkAdministration(config *docker.HostConfig) { | ||||
| 	config.CapAdd = append(config.CapAdd, "NET_ADMIN") | ||||
| 	config.Mounts = append(config.Mounts, docker.HostMount{ | ||||
| 		Type:   "bind", | ||||
| 		Source: "/dev/net/tun", | ||||
| 		Target: "/dev/net/tun", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -164,9 +164,7 @@ func (s *IntegrationTestSuite) tailscaleContainer( | ||||
| 		Name:     hostname, | ||||
| 		Networks: []*dockertest.Network{&s.network}, | ||||
| 		Cmd: []string{ | ||||
| 			"tailscaled", | ||||
| 			"--tun=userspace-networking", | ||||
| 			"--socks5-server=localhost:1055", | ||||
| 			"tailscaled", "--tun=tsdev", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| @ -174,6 +172,8 @@ func (s *IntegrationTestSuite) tailscaleContainer( | ||||
| 		tailscaleBuildOptions, | ||||
| 		tailscaleOptions, | ||||
| 		DockerRestartPolicy, | ||||
| 		DockerAllowLocalIPv6, | ||||
| 		DockerAllowNetworkAdministration, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Could not start resource: %s", err) | ||||
| @ -372,29 +372,33 @@ func (s *IntegrationTestSuite) TestListNodes() { | ||||
| 
 | ||||
| func (s *IntegrationTestSuite) TestGetIpAddresses() { | ||||
| 	for _, scales := range s.namespaces { | ||||
| 		ipPrefix := netaddr.MustParseIPPrefix("100.64.0.0/10") | ||||
| 		ips, err := getIPs(scales.tailscales) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 		for hostname := range scales.tailscales { | ||||
| 		for hostname, _ := range scales.tailscales { | ||||
| 			ips := ips[hostname] | ||||
| 			for _, ip := range ips { | ||||
| 				s.T().Run(hostname, func(t *testing.T) { | ||||
| 				ip, ok := ips[hostname] | ||||
| 
 | ||||
| 				assert.True(t, ok) | ||||
| 					assert.NotNil(t, ip) | ||||
| 
 | ||||
| 					fmt.Printf("IP for %s: %s\n", hostname, ip) | ||||
| 
 | ||||
| 					// c.Assert(ip.Valid(), check.IsTrue)
 | ||||
| 				assert.True(t, ip.Is4()) | ||||
| 				assert.True(t, ipPrefix.Contains(ip)) | ||||
| 					assert.True(t, ip.Is4() || ip.Is6()) | ||||
| 					switch { | ||||
| 					case ip.Is4(): | ||||
| 						assert.True(t, IpPrefix4.Contains(ip)) | ||||
| 					case ip.Is6(): | ||||
| 						assert.True(t, IpPrefix6.Contains(ip)) | ||||
| 					} | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TODO(kradalby): fix this test
 | ||||
| // We need some way to impot ipnstate.Status from multiple go packages.
 | ||||
| // We need some way to import ipnstate.Status from multiple go packages.
 | ||||
| // Currently it will only work with 1.18.x since that is the last
 | ||||
| // version we have in go.mod
 | ||||
| // func (s *IntegrationTestSuite) TestStatus() {
 | ||||
| @ -448,16 +452,19 @@ func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP { | ||||
| 	return ips | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationTestSuite) TestPingAllPeers() { | ||||
| func (s *IntegrationTestSuite) TestPingAllPeersByAddress() { | ||||
| 	for _, scales := range s.namespaces { | ||||
| 		ips, err := getIPs(scales.tailscales) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 		for hostname, tailscale := range scales.tailscales { | ||||
| 			for peername, ip := range ips { | ||||
| 				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { | ||||
| 			for peername, peerIPs := range ips { | ||||
| 				for i, ip := range peerIPs { | ||||
| 					// We currently cant ping ourselves, so skip that.
 | ||||
| 					if peername != hostname { | ||||
| 					if peername == hostname { | ||||
| 						continue | ||||
| 					} | ||||
| 					s.T().Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) { | ||||
| 						// We are only interested in "direct ping" which means what we
 | ||||
| 						// might need a couple of more attempts before reaching the node.
 | ||||
| 						command := []string{ | ||||
| @ -469,9 +476,8 @@ func (s *IntegrationTestSuite) TestPingAllPeers() { | ||||
| 						} | ||||
| 
 | ||||
| 						fmt.Printf( | ||||
| 							"Pinging from %s (%s) to %s (%s)\n", | ||||
| 							"Pinging from %s to %s (%s)\n", | ||||
| 							hostname, | ||||
| 							ips[hostname], | ||||
| 							peername, | ||||
| 							ip, | ||||
| 						) | ||||
| @ -483,11 +489,11 @@ func (s *IntegrationTestSuite) TestPingAllPeers() { | ||||
| 						assert.Nil(t, err) | ||||
| 						fmt.Printf("Result for %s: %s\n", hostname, result) | ||||
| 						assert.Contains(t, result, "pong") | ||||
| 					} | ||||
| 					}) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationTestSuite) TestSharedNodes() { | ||||
| @ -553,17 +559,17 @@ func (s *IntegrationTestSuite) TestSharedNodes() { | ||||
| 	// TODO(juanfont): We have to find out why do we need to wait
 | ||||
| 	time.Sleep(100 * time.Second) // Wait for the nodes to receive updates
 | ||||
| 
 | ||||
| 	mainIps, err := getIPs(main.tailscales) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	sharedIps, err := getIPs(shared.tailscales) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	for hostname, tailscale := range main.tailscales { | ||||
| 		for peername, ip := range sharedIps { | ||||
| 			s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { | ||||
| 		for peername, peerIPs := range sharedIps { | ||||
| 			for i, ip := range peerIPs { | ||||
| 				// We currently cant ping ourselves, so skip that.
 | ||||
| 				if peername != hostname { | ||||
| 				if peername == hostname { | ||||
| 					continue | ||||
| 				} | ||||
| 				s.T().Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) { | ||||
| 					// We are only interested in "direct ping" which means what we
 | ||||
| 					// might need a couple of more attempts before reaching the node.
 | ||||
| 					command := []string{ | ||||
| @ -575,9 +581,8 @@ func (s *IntegrationTestSuite) TestSharedNodes() { | ||||
| 					} | ||||
| 
 | ||||
| 					fmt.Printf( | ||||
| 						"Pinging from %s (%s) to %s (%s)\n", | ||||
| 						"Pinging from %s to %s (%s)\n", | ||||
| 						hostname, | ||||
| 						mainIps[hostname], | ||||
| 						peername, | ||||
| 						ip, | ||||
| 					) | ||||
| @ -589,19 +594,29 @@ func (s *IntegrationTestSuite) TestSharedNodes() { | ||||
| 					assert.Nil(t, err) | ||||
| 					fmt.Printf("Result for %s: %s\n", hostname, result) | ||||
| 					assert.Contains(t, result, "pong") | ||||
| 				} | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationTestSuite) TestTailDrop() { | ||||
| 	for _, scales := range s.namespaces { | ||||
| 		ips, err := getIPs(scales.tailscales) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 		apiURLs, err := getAPIURLs(scales.tailscales) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 		retry := func(times int, sleepInverval time.Duration, doWork func() error) (err error) { | ||||
| 			for attempts := 0; attempts < times; attempts++ { | ||||
| 				err = doWork() | ||||
| 				if err == nil { | ||||
| 					return | ||||
| 				} | ||||
| 				time.Sleep(sleepInverval) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		for hostname, tailscale := range scales.tailscales { | ||||
| 			command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)} | ||||
| 			_, err := ExecuteCommand( | ||||
| @ -610,63 +625,31 @@ func (s *IntegrationTestSuite) TestTailDrop() { | ||||
| 				[]string{}, | ||||
| 			) | ||||
| 			assert.Nil(s.T(), err) | ||||
| 			for peername, ip := range ips { | ||||
| 				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { | ||||
| 					if peername != hostname { | ||||
| 						// Under normal circumstances, we should be able to send a file
 | ||||
| 						// using `tailscale file cp` - but not in userspace networking mode
 | ||||
| 						// So curl!
 | ||||
| 						peerAPI, ok := apiURLs[ip] | ||||
| 						assert.True(t, ok) | ||||
| 
 | ||||
| 						// TODO(juanfont): We still have some issues with the test infrastructure, so
 | ||||
| 						// lets run curl multiple times until it works.
 | ||||
| 						attempts := 0 | ||||
| 						var err error | ||||
| 						for { | ||||
| 							command := []string{ | ||||
| 								"curl", | ||||
| 								"--retry-connrefused", | ||||
| 								"--retry-delay", | ||||
| 								"30", | ||||
| 								"--retry", | ||||
| 								"10", | ||||
| 								"--connect-timeout", | ||||
| 								"60", | ||||
| 								"-X", | ||||
| 								"PUT", | ||||
| 								"--upload-file", | ||||
| 								fmt.Sprintf("/tmp/file_from_%s", hostname), | ||||
| 								fmt.Sprintf( | ||||
| 									"%s/v0/put/file_from_%s", | ||||
| 									peerAPI, | ||||
| 									hostname, | ||||
| 								), | ||||
| 			for peername, _ := range ips { | ||||
| 				if peername == hostname { | ||||
| 					continue | ||||
| 				} | ||||
| 				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { | ||||
| 					command := []string{ | ||||
| 						"tailscale", "file", "cp", | ||||
| 						fmt.Sprintf("/tmp/file_from_%s", hostname), | ||||
| 						fmt.Sprintf("%s:", peername), | ||||
| 					} | ||||
| 					retry(10, 1*time.Second, func() error { | ||||
| 						fmt.Printf( | ||||
| 								"Sending file from %s (%s) to %s (%s)\n", | ||||
| 							"Sending file from %s to %s\n", | ||||
| 							hostname, | ||||
| 								ips[hostname], | ||||
| 							peername, | ||||
| 								ip, | ||||
| 						) | ||||
| 							_, err = ExecuteCommand( | ||||
| 						_, err := ExecuteCommand( | ||||
| 							&tailscale, | ||||
| 							command, | ||||
| 								[]string{"ALL_PROXY=socks5://localhost:1055"}, | ||||
| 							[]string{}, | ||||
| 							ExecuteCommandTimeout(60*time.Second), | ||||
| 						) | ||||
| 							if err == nil { | ||||
| 								break | ||||
| 							} else { | ||||
| 								time.Sleep(10 * time.Second) | ||||
| 								attempts++ | ||||
| 								if attempts > 10 { | ||||
| 									break | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 						return err | ||||
| 					}) | ||||
| 					assert.Nil(t, err) | ||||
| 					} | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| @ -684,8 +667,10 @@ func (s *IntegrationTestSuite) TestTailDrop() { | ||||
| 			) | ||||
| 			assert.Nil(s.T(), err) | ||||
| 			for peername, ip := range ips { | ||||
| 				if peername == hostname { | ||||
| 					continue | ||||
| 				} | ||||
| 				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { | ||||
| 					if peername != hostname { | ||||
| 					command := []string{ | ||||
| 						"ls", | ||||
| 						fmt.Sprintf("/tmp/file_from_%s", peername), | ||||
| @ -706,10 +691,46 @@ func (s *IntegrationTestSuite) TestTailDrop() { | ||||
| 					fmt.Printf("Result for %s: %s\n", peername, result) | ||||
| 					assert.Equal( | ||||
| 						t, | ||||
| 							result, | ||||
| 						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,23 +742,20 @@ func (s *IntegrationTestSuite) TestMagicDNS() { | ||||
| 		ips, err := getIPs(scales.tailscales) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 		for hostname, tailscale := range scales.tailscales { | ||||
| 			for peername, ip := range ips { | ||||
| 			for peername, ips := range ips { | ||||
| 				if peername == hostname { | ||||
| 					continue | ||||
| 				} | ||||
| 				s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { | ||||
| 					if peername != hostname { | ||||
| 					command := []string{ | ||||
| 							"tailscale", "ping", | ||||
| 							"--timeout=10s", | ||||
| 							"--c=20", | ||||
| 							"--until-direct=true", | ||||
| 						"tailscale", "ip", | ||||
| 						fmt.Sprintf("%s.%s.headscale.net", peername, namespace), | ||||
| 					} | ||||
| 
 | ||||
| 					fmt.Printf( | ||||
| 							"Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n", | ||||
| 							hostname, | ||||
| 							ips[hostname], | ||||
| 						"Resolving name %s from %s\n", | ||||
| 						peername, | ||||
| 							ip, | ||||
| 						hostname, | ||||
| 					) | ||||
| 					result, err := ExecuteCommand( | ||||
| 						&tailscale, | ||||
| @ -746,7 +764,9 @@ func (s *IntegrationTestSuite) TestMagicDNS() { | ||||
| 					) | ||||
| 					assert.Nil(t, err) | ||||
| 					fmt.Printf("Result for %s: %s\n", hostname, result) | ||||
| 						assert.Contains(t, result, "pong") | ||||
| 
 | ||||
| 					for _, ip := range ips { | ||||
| 						assert.Contains(t, result, ip.String()) | ||||
| 					} | ||||
| 				}) | ||||
| 			} | ||||
| @ -754,8 +774,8 @@ func (s *IntegrationTestSuite) TestMagicDNS() { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, error) { | ||||
| 	ips := make(map[string]netaddr.IP) | ||||
| func getIPs(tailscales map[string]dockertest.Resource) (map[string][]netaddr.IP, error) { | ||||
| 	ips := make(map[string][]netaddr.IP) | ||||
| 	for hostname, tailscale := range tailscales { | ||||
| 		command := []string{"tailscale", "ip"} | ||||
| 
 | ||||
| @ -768,12 +788,17 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		ip, err := netaddr.ParseIP(strings.TrimSuffix(result, "\n")) | ||||
| 		for _, address := range strings.Split(result, "\n") { | ||||
| 			address = strings.TrimSuffix(address, "\n") | ||||
| 			if len(address) < 1 { | ||||
| 				continue | ||||
| 			} | ||||
| 			ip, err := netaddr.ParseIP(address) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 		ips[hostname] = ip | ||||
| 			ips[hostname] = append(ips[hostname], ip) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ips, nil | ||||
|  | ||||
| @ -2,6 +2,9 @@ log_level: trace | ||||
| acl_policy_path: "" | ||||
| db_type: sqlite3 | ||||
| ephemeral_node_inactivity_timeout: 30m | ||||
| ip_prefixes: | ||||
|   - fd7a:115c:a1e0::/48 | ||||
|   - 100.64.0.0/10 | ||||
| dns_config: | ||||
|   base_domain: headscale.net | ||||
|   magic_dns: true | ||||
|  | ||||
							
								
								
									
										83
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								machine.go
									
									
									
									
									
								
							| @ -1,6 +1,7 @@ | ||||
| package headscale | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql/driver" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| @ -23,6 +24,7 @@ const ( | ||||
| 	errMachineNotFound            = Error("machine not found") | ||||
| 	errMachineAlreadyRegistered   = Error("machine already registered") | ||||
| 	errMachineRouteIsNotAvailable = Error("route is not available on machine") | ||||
| 	errMachineAddressesInvalid    = Error("failed to parse machine addresses") | ||||
| ) | ||||
| 
 | ||||
| // Machine is a Headscale client.
 | ||||
| @ -31,7 +33,7 @@ type Machine struct { | ||||
| 	MachineKey  string `gorm:"type:varchar(64);unique_index"` | ||||
| 	NodeKey     string | ||||
| 	DiscoKey    string | ||||
| 	IPAddress   string | ||||
| 	IPAddresses MachineAddresses | ||||
| 	Name        string | ||||
| 	NamespaceID uint | ||||
| 	Namespace   Namespace `gorm:"foreignKey:NamespaceID"` | ||||
| @ -64,6 +66,47 @@ func (machine Machine) isRegistered() bool { | ||||
| 	return machine.Registered | ||||
| } | ||||
| 
 | ||||
| type MachineAddresses []netaddr.IP | ||||
| 
 | ||||
| func (ma MachineAddresses) ToStringSlice() []string { | ||||
| 	strSlice := make([]string, 0, len(ma)) | ||||
| 	for _, addr := range ma { | ||||
| 		strSlice = append(strSlice, addr.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	return strSlice | ||||
| } | ||||
| 
 | ||||
| func (ma *MachineAddresses) Scan(destination interface{}) error { | ||||
| 	switch value := destination.(type) { | ||||
| 	case string: | ||||
| 		addresses := strings.Split(value, ",") | ||||
| 		*ma = (*ma)[:0] | ||||
| 		for _, addr := range addresses { | ||||
| 			if len(addr) < 1 { | ||||
| 				continue | ||||
| 			} | ||||
| 			parsed, err := netaddr.ParseIP(addr) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			*ma = append(*ma, parsed) | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 
 | ||||
| 	default: | ||||
| 		return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Value return json value, implement driver.Valuer interface.
 | ||||
| func (ma MachineAddresses) Value() (driver.Value, error) { | ||||
| 	addresses := strings.Join(ma.ToStringSlice(), ",") | ||||
| 
 | ||||
| 	return addresses, nil | ||||
| } | ||||
| 
 | ||||
| // isExpired returns whether the machine registration has expired.
 | ||||
| func (machine Machine) isExpired() bool { | ||||
| 	// If Expiry is not set, the client has not indicated that
 | ||||
| @ -385,14 +428,18 @@ func (h *Headscale) isOutdated(machine *Machine) bool { | ||||
| 	} | ||||
| 
 | ||||
| 	lastChange := h.getLastStateChange(namespaces...) | ||||
| 	lastUpdate := machine.CreatedAt | ||||
| 	if machine.LastSuccessfulUpdate != nil { | ||||
| 		lastUpdate = *machine.LastSuccessfulUpdate | ||||
| 	} | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Time("last_successful_update", *machine.LastSuccessfulUpdate). | ||||
| 		Time("last_state_change", lastChange). | ||||
| 		Time("last_successful_update", lastChange). | ||||
| 		Time("last_state_change", lastUpdate). | ||||
| 		Msgf("Checking if %s is missing updates", machine.Name) | ||||
| 
 | ||||
| 	return machine.LastSuccessfulUpdate.Before(lastChange) | ||||
| 	return lastUpdate.Before(lastChange) | ||||
| } | ||||
| 
 | ||||
| func (machine Machine) String() string { | ||||
| @ -478,22 +525,12 @@ func (machine Machine) toNode( | ||||
| 	} | ||||
| 
 | ||||
| 	addrs := []netaddr.IPPrefix{} | ||||
| 	ip, err := netaddr.ParseIPPrefix(fmt.Sprintf("%s/32", machine.IPAddress)) | ||||
| 	if err != nil { | ||||
| 		log.Trace(). | ||||
| 			Caller(). | ||||
| 			Str("ip", machine.IPAddress). | ||||
| 			Msgf("Failed to parse IP Prefix from IP: %s", machine.IPAddress) | ||||
| 
 | ||||
| 		return nil, err | ||||
| 	for _, machineAddress := range machine.IPAddresses { | ||||
| 		ip := netaddr.IPPrefixFrom(machineAddress, machineAddress.BitLen()) | ||||
| 		addrs = append(addrs, ip) | ||||
| 	} | ||||
| 	addrs = append(addrs, ip) // missing the ipv6 ?
 | ||||
| 
 | ||||
| 	allowedIPs := []netaddr.IPPrefix{} | ||||
| 	allowedIPs = append( | ||||
| 		allowedIPs, | ||||
| 		ip, | ||||
| 	) // we append the node own IP, as it is required by the clients
 | ||||
| 	allowedIPs := append([]netaddr.IPPrefix{}, addrs...) // we append the node own IP, as it is required by the clients
 | ||||
| 
 | ||||
| 	if includeRoutes { | ||||
| 		routesStr := []string{} | ||||
| @ -602,7 +639,7 @@ func (machine *Machine) toProto() *v1.Machine { | ||||
| 
 | ||||
| 		NodeKey:     machine.NodeKey, | ||||
| 		DiscoKey:    machine.DiscoKey, | ||||
| 		IpAddress: machine.IPAddress, | ||||
| 		IpAddresses: machine.IPAddresses.ToStringSlice(), | ||||
| 		Name:        machine.Name, | ||||
| 		Namespace:   machine.Namespace.toProto(), | ||||
| 
 | ||||
| @ -703,7 +740,7 @@ func (h *Headscale) RegisterMachine( | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ip, err := h.getAvailableIP() | ||||
| 	ips, err := h.getAvailableIPs() | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| @ -717,10 +754,10 @@ func (h *Headscale) RegisterMachine( | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Str("ip", ip.String()). | ||||
| 		Str("ip", strings.Join(ips.ToStringSlice(), ",")). | ||||
| 		Msg("Found IP for host") | ||||
| 
 | ||||
| 	machine.IPAddress = ip.String() | ||||
| 	machine.IPAddresses = ips | ||||
| 	machine.NamespaceID = namespace.ID | ||||
| 	machine.Registered = true | ||||
| 	machine.RegisterMethod = RegisterMethodCLI | ||||
| @ -730,7 +767,7 @@ func (h *Headscale) RegisterMachine( | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Str("ip", ip.String()). | ||||
| 		Str("ip", strings.Join(ips.ToStringSlice(), ",")). | ||||
| 		Msg("Machine registered with the database") | ||||
| 
 | ||||
| 	return machine, nil | ||||
|  | ||||
| @ -6,6 +6,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"gopkg.in/check.v1" | ||||
| 	"inet.af/netaddr" | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestGetMachine(c *check.C) { | ||||
| @ -199,3 +200,24 @@ func (s *Suite) TestExpireMachine(c *check.C) { | ||||
| 
 | ||||
| 	c.Assert(machineFromDB.isExpired(), check.Equals, true) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) { | ||||
| 	input := MachineAddresses([]netaddr.IP{ | ||||
| 		netaddr.MustParseIP("192.0.2.1"), | ||||
| 		netaddr.MustParseIP("2001:db8::1"), | ||||
| 	}) | ||||
| 	serialized, err := input.Value() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	if serial, ok := serialized.(string); ok { | ||||
| 		c.Assert(serial, check.Equals, "192.0.2.1,2001:db8::1") | ||||
| 	} | ||||
| 
 | ||||
| 	var deserialized MachineAddresses | ||||
| 	err = deserialized.Scan(serialized) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	c.Assert(len(deserialized), check.Equals, len(input)) | ||||
| 	for i := range deserialized { | ||||
| 		c.Assert(deserialized[i], check.Equals, input[i]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"gopkg.in/check.v1" | ||||
| 	"gorm.io/gorm" | ||||
| 	"inet.af/netaddr" | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) { | ||||
| @ -146,7 +147,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.1", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared1) | ||||
| @ -164,7 +165,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.2", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared2.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared2) | ||||
| @ -182,7 +183,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.3", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared3.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared3) | ||||
| @ -200,7 +201,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.4", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(preAuthKey2Shared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine2InShared1) | ||||
|  | ||||
							
								
								
									
										5
									
								
								oidc.go
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								oidc.go
									
									
									
									
									
								
							| @ -126,6 +126,7 @@ var oidcCallbackTemplate = template.Must( | ||||
| 	</html>`), | ||||
| ) | ||||
| 
 | ||||
| // TODO: Why is the entire machine registration logic duplicated here?
 | ||||
| // OIDCCallback handles the callback from the OIDC endpoint
 | ||||
| // Retrieves the mkey from the state cache and adds the machine to the users email namespace
 | ||||
| // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
 | ||||
| @ -316,7 +317,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 				return | ||||
| 			} | ||||
| 
 | ||||
| 			ip, err := h.getAvailableIP() | ||||
| 			ips, err := h.getAvailableIPs() | ||||
| 			if err != nil { | ||||
| 				log.Error(). | ||||
| 					Caller(). | ||||
| @ -330,7 +331,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 				return | ||||
| 			} | ||||
| 
 | ||||
| 			machine.IPAddress = ip.String() | ||||
| 			machine.IPAddresses = ips | ||||
| 			machine.NamespaceID = namespace.ID | ||||
| 			machine.Registered = true | ||||
| 			machine.RegisterMethod = RegisterMethodOIDC | ||||
|  | ||||
							
								
								
									
										91
									
								
								poll.go
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								poll.go
									
									
									
									
									
								
							| @ -1,8 +1,10 @@ | ||||
| package headscale | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| @ -154,14 +156,33 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { | ||||
| 		Str("id", ctx.Param("id")). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Msg("Loading or creating update channel") | ||||
| 	updateChan := make(chan struct{}) | ||||
| 
 | ||||
| 	pollDataChan := make(chan []byte) | ||||
| 	// TODO: could probably remove all that duplication once generics land.
 | ||||
| 	closeChanWithLog := func(channel interface{}, name string) { | ||||
| 		log.Trace(). | ||||
| 			Str("handler", "PollNetMap"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Str("channel", "Done"). | ||||
| 			Msg(fmt.Sprintf("Closing %s channel", name)) | ||||
| 
 | ||||
| 		switch c := channel.(type) { | ||||
| 		case (chan struct{}): | ||||
| 			close(c) | ||||
| 
 | ||||
| 		case (chan []byte): | ||||
| 			close(c) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	const chanSize = 8 | ||||
| 	updateChan := make(chan struct{}, chanSize) | ||||
| 	defer closeChanWithLog(updateChan, "updateChan") | ||||
| 
 | ||||
| 	pollDataChan := make(chan []byte, chanSize) | ||||
| 	defer closeChanWithLog(pollDataChan, "pollDataChan") | ||||
| 
 | ||||
| 	keepAliveChan := make(chan []byte) | ||||
| 
 | ||||
| 	cancelKeepAlive := make(chan struct{}) | ||||
| 	defer close(cancelKeepAlive) | ||||
| 	defer closeChanWithLog(keepAliveChan, "keepAliveChan") | ||||
| 
 | ||||
| 	if req.OmitPeers && !req.Stream { | ||||
| 		log.Info(). | ||||
| @ -174,7 +195,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { | ||||
| 		// even tho the comments in the tailscale code dont explicitly say so.
 | ||||
| 		updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update"). | ||||
| 			Inc() | ||||
| 		go func() { updateChan <- struct{}{} }() | ||||
| 		updateChan <- struct{}{} | ||||
| 
 | ||||
| 		return | ||||
| 	} else if req.OmitPeers && req.Stream { | ||||
| @ -195,7 +216,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { | ||||
| 		Str("handler", "PollNetMap"). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Msg("Sending initial map") | ||||
| 	go func() { pollDataChan <- data }() | ||||
| 	pollDataChan <- data | ||||
| 
 | ||||
| 	log.Info(). | ||||
| 		Str("handler", "PollNetMap"). | ||||
| @ -203,7 +224,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { | ||||
| 		Msg("Notifying peers") | ||||
| 	updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update"). | ||||
| 		Inc() | ||||
| 	go func() { updateChan <- struct{}{} }() | ||||
| 	updateChan <- struct{}{} | ||||
| 
 | ||||
| 	h.PollNetMapStream( | ||||
| 		ctx, | ||||
| @ -213,7 +234,6 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { | ||||
| 		pollDataChan, | ||||
| 		keepAliveChan, | ||||
| 		updateChan, | ||||
| 		cancelKeepAlive, | ||||
| 	) | ||||
| 	log.Trace(). | ||||
| 		Str("handler", "PollNetMap"). | ||||
| @ -233,16 +253,20 @@ func (h *Headscale) PollNetMapStream( | ||||
| 	pollDataChan chan []byte, | ||||
| 	keepAliveChan chan []byte, | ||||
| 	updateChan chan struct{}, | ||||
| 	cancelKeepAlive chan struct{}, | ||||
| ) { | ||||
| 	{ | ||||
| 		ctx, cancel := context.WithCancel(ctx.Request.Context()) | ||||
| 		defer cancel() | ||||
| 
 | ||||
| 		go h.scheduledPollWorker( | ||||
| 		cancelKeepAlive, | ||||
| 			ctx, | ||||
| 			updateChan, | ||||
| 			keepAliveChan, | ||||
| 			machineKey, | ||||
| 			mapRequest, | ||||
| 			machine, | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Stream(func(writer io.Writer) bool { | ||||
| 		log.Trace(). | ||||
| @ -392,10 +416,14 @@ func (h *Headscale) PollNetMapStream( | ||||
| 			updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name). | ||||
| 				Inc() | ||||
| 			if h.isOutdated(machine) { | ||||
| 				var lastUpdate time.Time | ||||
| 				if machine.LastSuccessfulUpdate != nil { | ||||
| 					lastUpdate = *machine.LastSuccessfulUpdate | ||||
| 				} | ||||
| 				log.Debug(). | ||||
| 					Str("handler", "PollNetMapStream"). | ||||
| 					Str("machine", machine.Name). | ||||
| 					Time("last_successful_update", *machine.LastSuccessfulUpdate). | ||||
| 					Time("last_successful_update", lastUpdate). | ||||
| 					Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)). | ||||
| 					Msgf("There has been updates since the last successful update to %s", machine.Name) | ||||
| 				data, err := h.getMapResponse(machineKey, mapRequest, machine) | ||||
| @ -464,10 +492,14 @@ func (h *Headscale) PollNetMapStream( | ||||
| 						Msg("Cannot update machine LastSuccessfulUpdate") | ||||
| 				} | ||||
| 			} else { | ||||
| 				var lastUpdate time.Time | ||||
| 				if machine.LastSuccessfulUpdate != nil { | ||||
| 					lastUpdate = *machine.LastSuccessfulUpdate | ||||
| 				} | ||||
| 				log.Trace(). | ||||
| 					Str("handler", "PollNetMapStream"). | ||||
| 					Str("machine", machine.Name). | ||||
| 					Time("last_successful_update", *machine.LastSuccessfulUpdate). | ||||
| 					Time("last_successful_update", lastUpdate). | ||||
| 					Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)). | ||||
| 					Msgf("%s is up to date", machine.Name) | ||||
| 			} | ||||
| @ -507,42 +539,13 @@ func (h *Headscale) PollNetMapStream( | ||||
| 					Msg("Cannot update machine LastSeen") | ||||
| 			} | ||||
| 
 | ||||
| 			log.Trace(). | ||||
| 				Str("handler", "PollNetMapStream"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Str("channel", "Done"). | ||||
| 				Msg("Cancelling keepAlive channel") | ||||
| 			cancelKeepAlive <- struct{}{} | ||||
| 
 | ||||
| 			log.Trace(). | ||||
| 				Str("handler", "PollNetMapStream"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Str("channel", "Done"). | ||||
| 				Msg("Closing update channel") | ||||
| 			// h.closeUpdateChannel(m)
 | ||||
| 			close(updateChan) | ||||
| 
 | ||||
| 			log.Trace(). | ||||
| 				Str("handler", "PollNetMapStream"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Str("channel", "Done"). | ||||
| 				Msg("Closing pollData channel") | ||||
| 			close(pollDataChan) | ||||
| 
 | ||||
| 			log.Trace(). | ||||
| 				Str("handler", "PollNetMapStream"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Str("channel", "Done"). | ||||
| 				Msg("Closing keepAliveChan channel") | ||||
| 			close(keepAliveChan) | ||||
| 
 | ||||
| 			return false | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) scheduledPollWorker( | ||||
| 	cancelChan <-chan struct{}, | ||||
| 	ctx context.Context, | ||||
| 	updateChan chan<- struct{}, | ||||
| 	keepAliveChan chan<- []byte, | ||||
| 	machineKey key.MachinePublic, | ||||
| @ -554,7 +557,7 @@ func (h *Headscale) scheduledPollWorker( | ||||
| 
 | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-cancelChan: | ||||
| 		case <-ctx.Done(): | ||||
| 			return | ||||
| 
 | ||||
| 		case <-keepAliveTicker.C: | ||||
|  | ||||
| @ -18,7 +18,7 @@ message Machine { | ||||
|     string machine_key  = 2; | ||||
|     string node_key     = 3; | ||||
|     string disco_key    = 4; | ||||
|     string ip_address   = 5; | ||||
|     repeated string ip_addresses   = 5; | ||||
|     string name         = 6; | ||||
|     Namespace namespace = 7; | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ package headscale | ||||
| 
 | ||||
| import ( | ||||
| 	"gopkg.in/check.v1" | ||||
| 	"inet.af/netaddr" | ||||
| ) | ||||
| 
 | ||||
| func CreateNodeNamespace( | ||||
| @ -26,7 +27,7 @@ func CreateNodeNamespace( | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      ip, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(pak1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine) | ||||
| @ -214,7 +215,7 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) { | ||||
| 		NamespaceID:    namespace1.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.4", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(pak4.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine4) | ||||
| @ -294,7 +295,7 @@ func (s *Suite) TestDeleteSharedMachine(c *check.C) { | ||||
| 		NamespaceID:    namespace1.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddress:      "100.64.0.4", | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(pak4n1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine4) | ||||
|  | ||||
							
								
								
									
										75
									
								
								utils.go
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								utils.go
									
									
									
									
									
								
							| @ -133,61 +133,78 @@ func encode( | ||||
| 	return privKey.SealTo(*pubKey, b), nil | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) getAvailableIP() (*netaddr.IP, error) { | ||||
| 	ipPrefix := h.cfg.IPPrefix | ||||
| func (h *Headscale) getAvailableIPs() (ips MachineAddresses, err error) { | ||||
| 	ipPrefixes := h.cfg.IPPrefixes | ||||
| 	for _, ipPrefix := range ipPrefixes { | ||||
| 		var ip *netaddr.IP | ||||
| 		ip, err = h.getAvailableIP(ipPrefix) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		ips = append(ips, *ip) | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func GetIPPrefixEndpoints(na netaddr.IPPrefix) (network, broadcast netaddr.IP) { | ||||
| 	ipRange := na.Range() | ||||
| 	network = ipRange.From() | ||||
| 	broadcast = ipRange.To() | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // TODO: Is this concurrency safe?
 | ||||
| // What would happen if multiple hosts were to register at the same time?
 | ||||
| // Would we attempt to assign the same addresses to multiple nodes?
 | ||||
| func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, error) { | ||||
| 	usedIps, err := h.getUsedIPs() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ipPrefixNetworkAddress, ipPrefixBroadcastAddress := GetIPPrefixEndpoints(ipPrefix) | ||||
| 
 | ||||
| 	// Get the first IP in our prefix
 | ||||
| 	ip := ipPrefix.IP() | ||||
| 	ip := ipPrefixNetworkAddress.Next() | ||||
| 
 | ||||
| 	for { | ||||
| 		if !ipPrefix.Contains(ip) { | ||||
| 			return nil, errCouldNotAllocateIP | ||||
| 		} | ||||
| 
 | ||||
| 		// Some OS (including Linux) does not like when IPs ends with 0 or 255, which
 | ||||
| 		// is typically called network or broadcast. Lets avoid them and continue
 | ||||
| 		// to look when we get one of those traditionally reserved IPs.
 | ||||
| 		ipRaw := ip.As4() | ||||
| 		if ipRaw[3] == 0 || ipRaw[3] == 255 { | ||||
| 		switch { | ||||
| 		case ip.Compare(ipPrefixBroadcastAddress) == 0: | ||||
| 			fallthrough | ||||
| 		case containsIPs(usedIps, ip): | ||||
| 			fallthrough | ||||
| 		case ip.IsZero() || ip.IsLoopback(): | ||||
| 			ip = ip.Next() | ||||
| 
 | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if ip.IsZero() && | ||||
| 			ip.IsLoopback() { | ||||
| 			ip = ip.Next() | ||||
| 
 | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if !containsIPs(usedIps, ip) { | ||||
| 		default: | ||||
| 			return &ip, nil | ||||
| 		} | ||||
| 
 | ||||
| 		ip = ip.Next() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) { | ||||
| 	var addresses []string | ||||
| 	h.db.Model(&Machine{}).Pluck("ip_address", &addresses) | ||||
| 	// FIXME: This really deserves a better data model,
 | ||||
| 	// but this was quick to get running and it should be enough
 | ||||
| 	// to begin experimenting with a dual stack tailnet.
 | ||||
| 	var addressesSlices []string | ||||
| 	h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices) | ||||
| 
 | ||||
| 	ips := make([]netaddr.IP, len(addresses)) | ||||
| 	for index, addr := range addresses { | ||||
| 		if addr != "" { | ||||
| 			ip, err := netaddr.ParseIP(addr) | ||||
| 	ips := make([]netaddr.IP, 0, len(h.cfg.IPPrefixes)*len(addressesSlices)) | ||||
| 	for _, slice := range addressesSlices { | ||||
| 		var a MachineAddresses | ||||
| 		err := a.Scan(slice) | ||||
| 		if err != nil { | ||||
| 				return nil, fmt.Errorf("failed to parse ip from database: %w", err) | ||||
| 			} | ||||
| 
 | ||||
| 			ips[index] = ip | ||||
| 			return nil, fmt.Errorf("failed to read ip from database: %w", err) | ||||
| 		} | ||||
| 		ips = append(ips, a...) | ||||
| 	} | ||||
| 
 | ||||
| 	return ips, nil | ||||
|  | ||||
| @ -6,17 +6,18 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestGetAvailableIp(c *check.C) { | ||||
| 	ip, err := app.getAvailableIP() | ||||
| 	ips, err := app.getAvailableIPs() | ||||
| 
 | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	expected := netaddr.MustParseIP("10.27.0.1") | ||||
| 
 | ||||
| 	c.Assert(ip.String(), check.Equals, expected.String()) | ||||
| 	c.Assert(len(ips), check.Equals, 1) | ||||
| 	c.Assert(ips[0].String(), check.Equals, expected.String()) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetUsedIps(c *check.C) { | ||||
| 	ip, err := app.getAvailableIP() | ||||
| 	ips, err := app.getAvailableIPs() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test_ip") | ||||
| @ -38,22 +39,24 @@ func (s *Suite) TestGetUsedIps(c *check.C) { | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		IPAddress:      ip.String(), | ||||
| 		IPAddresses:    ips, | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| 
 | ||||
| 	ips, err := app.getUsedIPs() | ||||
| 	usedIps, err := app.getUsedIPs() | ||||
| 
 | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	expected := netaddr.MustParseIP("10.27.0.1") | ||||
| 
 | ||||
| 	c.Assert(ips[0], check.Equals, expected) | ||||
| 	c.Assert(len(usedIps), check.Equals, 1) | ||||
| 	c.Assert(usedIps[0], check.Equals, expected) | ||||
| 
 | ||||
| 	machine1, err := app.GetMachineByID(0) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	c.Assert(machine1.IPAddress, check.Equals, expected.String()) | ||||
| 	c.Assert(len(machine1.IPAddresses), check.Equals, 1) | ||||
| 	c.Assert(machine1.IPAddresses[0], check.Equals, expected) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| @ -61,7 +64,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	for index := 1; index <= 350; index++ { | ||||
| 		ip, err := app.getAvailableIP() | ||||
| 		ips, err := app.getAvailableIPs() | ||||
| 		c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 		pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil) | ||||
| @ -80,59 +83,64 @@ func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| 			Registered:     true, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(pak.ID), | ||||
| 			IPAddress:      ip.String(), | ||||
| 			IPAddresses:    ips, | ||||
| 		} | ||||
| 		app.db.Save(&machine) | ||||
| 	} | ||||
| 
 | ||||
| 	ips, err := app.getUsedIPs() | ||||
| 	usedIps, err := app.getUsedIPs() | ||||
| 
 | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	c.Assert(len(ips), check.Equals, 350) | ||||
| 	c.Assert(len(usedIps), check.Equals, 350) | ||||
| 
 | ||||
| 	c.Assert(ips[0], check.Equals, netaddr.MustParseIP("10.27.0.1")) | ||||
| 	c.Assert(ips[9], check.Equals, netaddr.MustParseIP("10.27.0.10")) | ||||
| 	c.Assert(ips[300], check.Equals, netaddr.MustParseIP("10.27.1.47")) | ||||
| 	c.Assert(usedIps[0], check.Equals, netaddr.MustParseIP("10.27.0.1")) | ||||
| 	c.Assert(usedIps[9], check.Equals, netaddr.MustParseIP("10.27.0.10")) | ||||
| 	c.Assert(usedIps[300], check.Equals, netaddr.MustParseIP("10.27.1.45")) | ||||
| 
 | ||||
| 	// Check that we can read back the IPs
 | ||||
| 	machine1, err := app.GetMachineByID(1) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(len(machine1.IPAddresses), check.Equals, 1) | ||||
| 	c.Assert( | ||||
| 		machine1.IPAddress, | ||||
| 		machine1.IPAddresses[0], | ||||
| 		check.Equals, | ||||
| 		netaddr.MustParseIP("10.27.0.1").String(), | ||||
| 		netaddr.MustParseIP("10.27.0.1"), | ||||
| 	) | ||||
| 
 | ||||
| 	machine50, err := app.GetMachineByID(50) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(len(machine50.IPAddresses), check.Equals, 1) | ||||
| 	c.Assert( | ||||
| 		machine50.IPAddress, | ||||
| 		machine50.IPAddresses[0], | ||||
| 		check.Equals, | ||||
| 		netaddr.MustParseIP("10.27.0.50").String(), | ||||
| 		netaddr.MustParseIP("10.27.0.50"), | ||||
| 	) | ||||
| 
 | ||||
| 	expectedNextIP := netaddr.MustParseIP("10.27.1.97") | ||||
| 	nextIP, err := app.getAvailableIP() | ||||
| 	expectedNextIP := netaddr.MustParseIP("10.27.1.95") | ||||
| 	nextIP, err := app.getAvailableIPs() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	c.Assert(nextIP.String(), check.Equals, expectedNextIP.String()) | ||||
| 	c.Assert(len(nextIP), check.Equals, 1) | ||||
| 	c.Assert(nextIP[0].String(), check.Equals, expectedNextIP.String()) | ||||
| 
 | ||||
| 	// If we call get Available again, we should receive
 | ||||
| 	// the same IP, as it has not been reserved.
 | ||||
| 	nextIP2, err := app.getAvailableIP() | ||||
| 	nextIP2, err := app.getAvailableIPs() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	c.Assert(nextIP2.String(), check.Equals, expectedNextIP.String()) | ||||
| 	c.Assert(len(nextIP2), check.Equals, 1) | ||||
| 	c.Assert(nextIP2[0].String(), check.Equals, expectedNextIP.String()) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) { | ||||
| 	ip, err := app.getAvailableIP() | ||||
| 	ips, err := app.getAvailableIPs() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	expected := netaddr.MustParseIP("10.27.0.1") | ||||
| 
 | ||||
| 	c.Assert(ip.String(), check.Equals, expected.String()) | ||||
| 	c.Assert(len(ips), check.Equals, 1) | ||||
| 	c.Assert(ips[0].String(), check.Equals, expected.String()) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test_ip") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| @ -156,8 +164,9 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) { | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| 
 | ||||
| 	ip2, err := app.getAvailableIP() | ||||
| 	ips2, err := app.getAvailableIPs() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	c.Assert(ip2.String(), check.Equals, expected.String()) | ||||
| 	c.Assert(len(ips2), check.Equals, 1) | ||||
| 	c.Assert(ips2[0].String(), check.Equals, expected.String()) | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user