mirror of
https://github.com/netbirdio/netbird.git
synced 2026-06-06 07:59:54 +00:00
Compare commits
47 Commits
sync-clien
...
test-ldfla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
619f1588b3 | ||
|
|
60d2a2c7df | ||
|
|
ff9585735b | ||
|
|
194951c88d | ||
|
|
d63f2e5196 | ||
|
|
14b5637555 | ||
|
|
3d3b05c157 | ||
|
|
f36e206238 | ||
|
|
9a808244d7 | ||
|
|
2dec76f8ea | ||
|
|
224bd8ff22 | ||
|
|
fe88a5662e | ||
|
|
f9f6409f94 | ||
|
|
b03154dce5 | ||
|
|
c57364596a | ||
|
|
2765bcfb89 | ||
|
|
fa6151b849 | ||
|
|
a939c1767c | ||
|
|
938554fb0f | ||
|
|
39bec2dd74 | ||
|
|
554c9bcf4b | ||
|
|
f3639675e7 | ||
|
|
a1457f541b | ||
|
|
9cdfb0d78c | ||
|
|
22d796097e | ||
|
|
aa39a5d528 | ||
|
|
1d2a5371ce | ||
|
|
6898e57686 | ||
|
|
c8bc865f2f | ||
|
|
06bb8658b1 | ||
|
|
8fc4fed3a0 | ||
|
|
df14f1399f | ||
|
|
6d6f090764 | ||
|
|
13febbbfca | ||
|
|
49d36b7e7e | ||
|
|
976787dbf1 | ||
|
|
536b0003ab | ||
|
|
0e9438d658 | ||
|
|
e570570fe5 | ||
|
|
23f9dd04b8 | ||
|
|
7a95bf5652 | ||
|
|
60c5782905 | ||
|
|
5a12c5d345 | ||
|
|
bdae55ab79 | ||
|
|
d01c3d5011 | ||
|
|
17a2af96ea | ||
|
|
cc595da1ad |
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Running pre-push hook..."
|
|
||||||
if ! make lint; then
|
|
||||||
echo ""
|
|
||||||
echo "Hint: To push without verification, run:"
|
|
||||||
echo " git push --no-verify"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "All checks passed!"
|
|
||||||
7
.github/workflows/golang-test-darwin.yml
vendored
7
.github/workflows/golang-test-darwin.yml
vendored
@@ -15,14 +15,13 @@ jobs:
|
|||||||
name: "Client / Unit"
|
name: "Client / Unit"
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
2
.github/workflows/golang-test-freebsd.yml
vendored
2
.github/workflows/golang-test-freebsd.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
release: "14.2"
|
release: "14.2"
|
||||||
prepare: |
|
prepare: |
|
||||||
pkg install -y curl pkgconf xorg
|
pkg install -y curl pkgconf xorg
|
||||||
GO_TARBALL="go1.24.10.freebsd-amd64.tar.gz"
|
GO_TARBALL="go1.23.12.freebsd-amd64.tar.gz"
|
||||||
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
||||||
curl -vLO "$GO_URL"
|
curl -vLO "$GO_URL"
|
||||||
tar -C /usr/local -vxzf "$GO_TARBALL"
|
tar -C /usr/local -vxzf "$GO_TARBALL"
|
||||||
|
|||||||
69
.github/workflows/golang-test-linux.yml
vendored
69
.github/workflows/golang-test-linux.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
@@ -106,15 +106,15 @@ jobs:
|
|||||||
arch: [ '386','amd64' ]
|
arch: [ '386','amd64' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
run: |
|
run: |
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
@@ -151,15 +151,15 @@ jobs:
|
|||||||
needs: [ build-cache ]
|
needs: [ build-cache ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
id: go-env
|
id: go-env
|
||||||
run: |
|
run: |
|
||||||
@@ -200,7 +200,7 @@ jobs:
|
|||||||
-e GOCACHE=${CONTAINER_GOCACHE} \
|
-e GOCACHE=${CONTAINER_GOCACHE} \
|
||||||
-e GOMODCACHE=${CONTAINER_GOMODCACHE} \
|
-e GOMODCACHE=${CONTAINER_GOMODCACHE} \
|
||||||
-e CONTAINER=${CONTAINER} \
|
-e CONTAINER=${CONTAINER} \
|
||||||
golang:1.24-alpine \
|
golang:1.23-alpine \
|
||||||
sh -c ' \
|
sh -c ' \
|
||||||
apk update; apk add --no-cache \
|
apk update; apk add --no-cache \
|
||||||
ca-certificates iptables ip6tables dbus dbus-dev libpcap-dev build-base; \
|
ca-certificates iptables ip6tables dbus dbus-dev libpcap-dev build-base; \
|
||||||
@@ -220,15 +220,15 @@ jobs:
|
|||||||
raceFlag: "-race"
|
raceFlag: "-race"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
||||||
@@ -270,15 +270,15 @@ jobs:
|
|||||||
arch: [ '386','amd64' ]
|
arch: [ '386','amd64' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
||||||
@@ -321,15 +321,15 @@ jobs:
|
|||||||
store: [ 'sqlite', 'postgres', 'mysql' ]
|
store: [ 'sqlite', 'postgres', 'mysql' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
run: |
|
run: |
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
@@ -408,16 +408,15 @@ jobs:
|
|||||||
-v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml \
|
-v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml \
|
||||||
-p 9090:9090 \
|
-p 9090:9090 \
|
||||||
prom/prometheus
|
prom/prometheus
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
run: |
|
run: |
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
@@ -498,15 +497,15 @@ jobs:
|
|||||||
-p 9090:9090 \
|
-p 9090:9090 \
|
||||||
prom/prometheus
|
prom/prometheus
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
run: |
|
run: |
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
@@ -562,15 +561,15 @@ jobs:
|
|||||||
store: [ 'sqlite', 'postgres']
|
store: [ 'sqlite', 'postgres']
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
run: |
|
run: |
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
|
|||||||
2
.github/workflows/golang-test-windows.yml
vendored
2
.github/workflows/golang-test-windows.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
id: go
|
id: go
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: Get Go environment
|
- name: Get Go environment
|
||||||
|
|||||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v3
|
uses: android-actions/setup-android@v3
|
||||||
with:
|
with:
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: Setup NDK
|
- name: Setup NDK
|
||||||
run: /usr/local/lib/android/sdk/cmdline-tools/7.0/bin/sdkmanager --install "ndk;23.1.7779620"
|
run: /usr/local/lib/android/sdk/cmdline-tools/7.0/bin/sdkmanager --install "ndk;23.1.7779620"
|
||||||
- name: install gomobile
|
- name: install gomobile
|
||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20251113184115-a159579294ab
|
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed
|
||||||
- name: gomobile init
|
- name: gomobile init
|
||||||
run: gomobile init
|
run: gomobile init
|
||||||
- name: build android netbird lib
|
- name: build android netbird lib
|
||||||
@@ -56,9 +56,9 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
- name: install gomobile
|
- name: install gomobile
|
||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20251113184115-a159579294ab
|
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed
|
||||||
- name: gomobile init
|
- name: gomobile init
|
||||||
run: gomobile init
|
run: gomobile init
|
||||||
- name: build iOS netbird lib
|
- name: build iOS netbird lib
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest-m
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
flags: ""
|
flags: ""
|
||||||
steps:
|
steps:
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -200,7 +200,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
@@ -67,13 +67,10 @@ jobs:
|
|||||||
- name: Install curl
|
- name: Install curl
|
||||||
run: sudo apt-get install -y curl
|
run: sudo apt-get install -y curl
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -83,6 +80,9 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup MySQL privileges
|
- name: Setup MySQL privileges
|
||||||
if: matrix.store == 'mysql'
|
if: matrix.store == 'mysql'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
10
.github/workflows/wasm-build-validation.yml
vendored
10
.github/workflows/wasm-build-validation.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
||||||
- name: Install golangci-lint
|
- name: Install golangci-lint
|
||||||
@@ -45,9 +45,9 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "1.23.x"
|
||||||
- name: Build Wasm client
|
- name: Build Wasm client
|
||||||
run: GOOS=js GOARCH=wasm go build -o netbird.wasm ./client/wasm/cmd
|
run: GOOS=js GOARCH=wasm go build -o netbird.wasm -ldflags="-s -w" ./client/wasm/cmd
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
- name: Check Wasm build size
|
- name: Check Wasm build size
|
||||||
@@ -60,8 +60,8 @@ jobs:
|
|||||||
|
|
||||||
echo "Size: ${SIZE} bytes (${SIZE_MB} MB)"
|
echo "Size: ${SIZE} bytes (${SIZE_MB} MB)"
|
||||||
|
|
||||||
if [ ${SIZE} -gt 57671680 ]; then
|
if [ ${SIZE} -gt 52428800 ]; then
|
||||||
echo "Wasm binary size (${SIZE_MB}MB) exceeds 55MB limit!"
|
echo "Wasm binary size (${SIZE_MB}MB) exceeds 50MB limit!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -136,14 +136,6 @@ checked out and set up:
|
|||||||
go mod tidy
|
go mod tidy
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Configure Git hooks for automatic linting:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make setup-hooks
|
|
||||||
```
|
|
||||||
|
|
||||||
This will configure Git to run linting automatically before each push, helping catch issues early.
|
|
||||||
|
|
||||||
### Dev Container Support
|
### Dev Container Support
|
||||||
|
|
||||||
If you prefer using a dev container for development, NetBird now includes support for dev containers.
|
If you prefer using a dev container for development, NetBird now includes support for dev containers.
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -1,27 +0,0 @@
|
|||||||
.PHONY: lint lint-all lint-install setup-hooks
|
|
||||||
GOLANGCI_LINT := $(shell pwd)/bin/golangci-lint
|
|
||||||
|
|
||||||
# Install golangci-lint locally if needed
|
|
||||||
$(GOLANGCI_LINT):
|
|
||||||
@echo "Installing golangci-lint..."
|
|
||||||
@mkdir -p ./bin
|
|
||||||
@GOBIN=$(shell pwd)/bin go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
||||||
|
|
||||||
# Lint only changed files (fast, for pre-push)
|
|
||||||
lint: $(GOLANGCI_LINT)
|
|
||||||
@echo "Running lint on changed files..."
|
|
||||||
@$(GOLANGCI_LINT) run --new-from-rev=origin/main --timeout=2m
|
|
||||||
|
|
||||||
# Lint entire codebase (slow, matches CI)
|
|
||||||
lint-all: $(GOLANGCI_LINT)
|
|
||||||
@echo "Running lint on all files..."
|
|
||||||
@$(GOLANGCI_LINT) run --timeout=12m
|
|
||||||
|
|
||||||
# Just install the linter
|
|
||||||
lint-install: $(GOLANGCI_LINT)
|
|
||||||
|
|
||||||
# Setup git hooks for all developers
|
|
||||||
setup-hooks:
|
|
||||||
@git config core.hooksPath .githooks
|
|
||||||
@chmod +x .githooks/pre-push
|
|
||||||
@echo "✅ Git hooks configured! Pre-push will now run 'make lint'"
|
|
||||||
@@ -4,13 +4,10 @@ package android
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
@@ -19,13 +16,10 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
"github.com/netbirdio/netbird/client/net"
|
"github.com/netbirdio/netbird/client/net"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/formatter"
|
"github.com/netbirdio/netbird/formatter"
|
||||||
"github.com/netbirdio/netbird/route"
|
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnectionListener export internal Listener for mobile
|
// ConnectionListener export internal Listener for mobile
|
||||||
@@ -68,18 +62,17 @@ type Client struct {
|
|||||||
deviceName string
|
deviceName string
|
||||||
uiVersion string
|
uiVersion string
|
||||||
networkChangeListener listener.NetworkChangeListener
|
networkChangeListener listener.NetworkChangeListener
|
||||||
stateFile string
|
|
||||||
|
|
||||||
connectClient *internal.ConnectClient
|
connectClient *internal.ConnectClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
func NewClient(platformFiles PlatformFiles, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
|
func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
|
||||||
execWorkaround(androidSDKVersion)
|
execWorkaround(androidSDKVersion)
|
||||||
|
|
||||||
net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket)
|
net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket)
|
||||||
return &Client{
|
return &Client{
|
||||||
cfgFile: platformFiles.ConfigurationFilePath(),
|
cfgFile: cfgFile,
|
||||||
deviceName: deviceName,
|
deviceName: deviceName,
|
||||||
uiVersion: uiVersion,
|
uiVersion: uiVersion,
|
||||||
tunAdapter: tunAdapter,
|
tunAdapter: tunAdapter,
|
||||||
@@ -87,12 +80,11 @@ func NewClient(platformFiles PlatformFiles, androidSDKVersion int, deviceName st
|
|||||||
recorder: peer.NewRecorder(""),
|
recorder: peer.NewRecorder(""),
|
||||||
ctxCancelLock: &sync.Mutex{},
|
ctxCancelLock: &sync.Mutex{},
|
||||||
networkChangeListener: networkChangeListener,
|
networkChangeListener: networkChangeListener,
|
||||||
stateFile: platformFiles.StateFilePath(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run start the internal client. It is a blocker function
|
// Run start the internal client. It is a blocker function
|
||||||
func (c *Client) Run(urlOpener URLOpener, isAndroidTV bool, dns *DNSList, dnsReadyListener DnsReadyListener, envList *EnvList) error {
|
func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsReadyListener, envList *EnvList) error {
|
||||||
exportEnvList(envList)
|
exportEnvList(envList)
|
||||||
cfg, err := profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
cfg, err := profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
||||||
ConfigPath: c.cfgFile,
|
ConfigPath: c.cfgFile,
|
||||||
@@ -115,7 +107,7 @@ func (c *Client) Run(urlOpener URLOpener, isAndroidTV bool, dns *DNSList, dnsRea
|
|||||||
c.ctxCancelLock.Unlock()
|
c.ctxCancelLock.Unlock()
|
||||||
|
|
||||||
auth := NewAuthWithConfig(ctx, cfg)
|
auth := NewAuthWithConfig(ctx, cfg)
|
||||||
err = auth.login(urlOpener, isAndroidTV)
|
err = auth.login(urlOpener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -123,7 +115,7 @@ func (c *Client) Run(urlOpener URLOpener, isAndroidTV bool, dns *DNSList, dnsRea
|
|||||||
// todo do not throw error in case of cancelled context
|
// todo do not throw error in case of cancelled context
|
||||||
ctx = internal.CtxInitState(ctx)
|
ctx = internal.CtxInitState(ctx)
|
||||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, c.stateFile)
|
return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
|
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
|
||||||
@@ -150,7 +142,7 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener
|
|||||||
// todo do not throw error in case of cancelled context
|
// todo do not throw error in case of cancelled context
|
||||||
ctx = internal.CtxInitState(ctx)
|
ctx = internal.CtxInitState(ctx)
|
||||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, c.stateFile)
|
return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the internal client and free the resources
|
// Stop the internal client and free the resources
|
||||||
@@ -164,19 +156,6 @@ func (c *Client) Stop() {
|
|||||||
c.ctxCancel()
|
c.ctxCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) RenewTun(fd int) error {
|
|
||||||
if c.connectClient == nil {
|
|
||||||
return fmt.Errorf("engine not running")
|
|
||||||
}
|
|
||||||
|
|
||||||
e := c.connectClient.Engine()
|
|
||||||
if e == nil {
|
|
||||||
return fmt.Errorf("engine not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.RenewTun(fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTraceLogLevel configure the logger to trace level
|
// SetTraceLogLevel configure the logger to trace level
|
||||||
func (c *Client) SetTraceLogLevel() {
|
func (c *Client) SetTraceLogLevel() {
|
||||||
log.SetLevel(log.TraceLevel)
|
log.SetLevel(log.TraceLevel)
|
||||||
@@ -198,7 +177,6 @@ func (c *Client) PeersList() *PeerInfoArray {
|
|||||||
p.IP,
|
p.IP,
|
||||||
p.FQDN,
|
p.FQDN,
|
||||||
p.ConnStatus.String(),
|
p.ConnStatus.String(),
|
||||||
PeerRoutes{routes: maps.Keys(p.GetRoutes())},
|
|
||||||
}
|
}
|
||||||
peerInfos[n] = pi
|
peerInfos[n] = pi
|
||||||
}
|
}
|
||||||
@@ -223,43 +201,31 @@ func (c *Client) Networks() *NetworkArray {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
routeSelector := routeManager.GetRouteSelector()
|
|
||||||
if routeSelector == nil {
|
|
||||||
log.Error("could not get route selector")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
networkArray := &NetworkArray{
|
networkArray := &NetworkArray{
|
||||||
items: make([]Network, 0),
|
items: make([]Network, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedDomains := c.recorder.GetResolvedDomainsStates()
|
|
||||||
|
|
||||||
for id, routes := range routeManager.GetClientRoutesWithNetID() {
|
for id, routes := range routeManager.GetClientRoutesWithNetID() {
|
||||||
if len(routes) == 0 {
|
if len(routes) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
r := routes[0]
|
r := routes[0]
|
||||||
domains := c.getNetworkDomainsFromRoute(r, resolvedDomains)
|
|
||||||
netStr := r.Network.String()
|
netStr := r.Network.String()
|
||||||
|
|
||||||
if r.IsDynamic() {
|
if r.IsDynamic() {
|
||||||
netStr = r.Domains.SafeString()
|
netStr = r.Domains.SafeString()
|
||||||
}
|
}
|
||||||
|
|
||||||
routePeer, err := c.recorder.GetPeer(routes[0].Peer)
|
peer, err := c.recorder.GetPeer(routes[0].Peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not get peer info for %s: %v", routes[0].Peer, err)
|
log.Errorf("could not get peer info for %s: %v", routes[0].Peer, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
network := Network{
|
network := Network{
|
||||||
Name: string(id),
|
Name: string(id),
|
||||||
Network: netStr,
|
Network: netStr,
|
||||||
Peer: routePeer.FQDN,
|
Peer: peer.FQDN,
|
||||||
Status: routePeer.ConnStatus.String(),
|
Status: peer.ConnStatus.String(),
|
||||||
IsSelected: routeSelector.IsSelected(id),
|
|
||||||
Domains: domains,
|
|
||||||
}
|
}
|
||||||
networkArray.Add(network)
|
networkArray.Add(network)
|
||||||
}
|
}
|
||||||
@@ -287,69 +253,6 @@ func (c *Client) RemoveConnectionListener() {
|
|||||||
c.recorder.RemoveConnectionListener()
|
c.recorder.RemoveConnectionListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) toggleRoute(command routeCommand) error {
|
|
||||||
return command.toggleRoute()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getRouteManager() (routemanager.Manager, error) {
|
|
||||||
client := c.connectClient
|
|
||||||
if client == nil {
|
|
||||||
return nil, fmt.Errorf("not connected")
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := client.Engine()
|
|
||||||
if engine == nil {
|
|
||||||
return nil, fmt.Errorf("engine is not running")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := engine.GetRouteManager()
|
|
||||||
if manager == nil {
|
|
||||||
return nil, fmt.Errorf("could not get route manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
return manager, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SelectRoute(route string) error {
|
|
||||||
manager, err := c.getRouteManager()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.toggleRoute(selectRouteCommand{route: route, manager: manager})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DeselectRoute(route string) error {
|
|
||||||
manager, err := c.getRouteManager()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.toggleRoute(deselectRouteCommand{route: route, manager: manager})
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNetworkDomainsFromRoute extracts domains from a route and enriches each domain
|
|
||||||
// with its resolved IP addresses from the provided resolvedDomains map.
|
|
||||||
func (c *Client) getNetworkDomainsFromRoute(route *route.Route, resolvedDomains map[domain.Domain]peer.ResolvedDomainInfo) NetworkDomains {
|
|
||||||
domains := NetworkDomains{}
|
|
||||||
|
|
||||||
for _, d := range route.Domains {
|
|
||||||
networkDomain := NetworkDomain{
|
|
||||||
Address: d.SafeString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if info, exists := resolvedDomains[d]; exists {
|
|
||||||
for _, prefix := range info.Prefixes {
|
|
||||||
networkDomain.addResolvedIP(prefix.Addr().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domains.Add(&networkDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains
|
|
||||||
}
|
|
||||||
|
|
||||||
func exportEnvList(list *EnvList) {
|
func exportEnvList(list *EnvList) {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type ErrListener interface {
|
|||||||
// URLOpener it is a callback interface. The Open function will be triggered if
|
// URLOpener it is a callback interface. The Open function will be triggered if
|
||||||
// the backend want to show an url for the user
|
// the backend want to show an url for the user
|
||||||
type URLOpener interface {
|
type URLOpener interface {
|
||||||
Open(url string, userCode string)
|
Open(string)
|
||||||
OnLoginSuccess()
|
OnLoginSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +148,9 @@ func (a *Auth) loginWithSetupKeyAndSaveConfig(setupKey string, deviceName string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login try register the client on the server
|
// Login try register the client on the server
|
||||||
func (a *Auth) Login(resultListener ErrListener, urlOpener URLOpener, isAndroidTV bool) {
|
func (a *Auth) Login(resultListener ErrListener, urlOpener URLOpener) {
|
||||||
go func() {
|
go func() {
|
||||||
err := a.login(urlOpener, isAndroidTV)
|
err := a.login(urlOpener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultListener.OnError(err)
|
resultListener.OnError(err)
|
||||||
} else {
|
} else {
|
||||||
@@ -159,7 +159,7 @@ func (a *Auth) Login(resultListener ErrListener, urlOpener URLOpener, isAndroidT
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) login(urlOpener URLOpener, isAndroidTV bool) error {
|
func (a *Auth) login(urlOpener URLOpener) error {
|
||||||
var needsLogin bool
|
var needsLogin bool
|
||||||
|
|
||||||
// check if we need to generate JWT token
|
// check if we need to generate JWT token
|
||||||
@@ -173,7 +173,7 @@ func (a *Auth) login(urlOpener URLOpener, isAndroidTV bool) error {
|
|||||||
|
|
||||||
jwtToken := ""
|
jwtToken := ""
|
||||||
if needsLogin {
|
if needsLogin {
|
||||||
tokenInfo, err := a.foregroundGetTokenInfo(urlOpener, isAndroidTV)
|
tokenInfo, err := a.foregroundGetTokenInfo(urlOpener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("interactive sso login failed: %v", err)
|
return fmt.Errorf("interactive sso login failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -199,8 +199,8 @@ func (a *Auth) login(urlOpener URLOpener, isAndroidTV bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener, isAndroidTV bool) (*auth.TokenInfo, error) {
|
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false, isAndroidTV, "")
|
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener, isAndroidTV bool) (*a
|
|||||||
return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err)
|
return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go urlOpener.Open(flowInfo.VerificationURIComplete, flowInfo.UserCode)
|
go urlOpener.Open(flowInfo.VerificationURIComplete)
|
||||||
|
|
||||||
waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second
|
waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second
|
||||||
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout)
|
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout)
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package android
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type ResolvedIPs struct {
|
|
||||||
resolvedIPs []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResolvedIPs) Add(ipAddress string) {
|
|
||||||
r.resolvedIPs = append(r.resolvedIPs, ipAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResolvedIPs) Get(i int) (string, error) {
|
|
||||||
if i < 0 || i >= len(r.resolvedIPs) {
|
|
||||||
return "", fmt.Errorf("%d is out of range", i)
|
|
||||||
}
|
|
||||||
return r.resolvedIPs[i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResolvedIPs) Size() int {
|
|
||||||
return len(r.resolvedIPs)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkDomain struct {
|
|
||||||
Address string
|
|
||||||
resolvedIPs ResolvedIPs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *NetworkDomain) addResolvedIP(resolvedIP string) {
|
|
||||||
d.resolvedIPs.Add(resolvedIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *NetworkDomain) GetResolvedIPs() *ResolvedIPs {
|
|
||||||
return &d.resolvedIPs
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkDomains struct {
|
|
||||||
domains []*NetworkDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkDomains) Add(domain *NetworkDomain) {
|
|
||||||
n.domains = append(n.domains, domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkDomains) Get(i int) (*NetworkDomain, error) {
|
|
||||||
if i < 0 || i >= len(n.domains) {
|
|
||||||
return nil, fmt.Errorf("%d is out of range", i)
|
|
||||||
}
|
|
||||||
return n.domains[i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkDomains) Size() int {
|
|
||||||
return len(n.domains)
|
|
||||||
}
|
|
||||||
@@ -3,16 +3,10 @@
|
|||||||
package android
|
package android
|
||||||
|
|
||||||
type Network struct {
|
type Network struct {
|
||||||
Name string
|
Name string
|
||||||
Network string
|
Network string
|
||||||
Peer string
|
Peer string
|
||||||
Status string
|
Status string
|
||||||
IsSelected bool
|
|
||||||
Domains NetworkDomains
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Network) GetNetworkDomains() *NetworkDomains {
|
|
||||||
return &n.Domains
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkArray struct {
|
type NetworkArray struct {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package android
|
package android
|
||||||
|
|
||||||
// PeerInfo describe information about the peers. It designed for the UI usage
|
// PeerInfo describe information about the peers. It designed for the UI usage
|
||||||
@@ -7,11 +5,6 @@ type PeerInfo struct {
|
|||||||
IP string
|
IP string
|
||||||
FQDN string
|
FQDN string
|
||||||
ConnStatus string // Todo replace to enum
|
ConnStatus string // Todo replace to enum
|
||||||
Routes PeerRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PeerInfo) GetPeerRoutes() *PeerRoutes {
|
|
||||||
return &p.Routes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerInfoArray is a wrapper of []PeerInfo
|
// PeerInfoArray is a wrapper of []PeerInfo
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package android
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type PeerRoutes struct {
|
|
||||||
routes []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PeerRoutes) Get(i int) (string, error) {
|
|
||||||
if i < 0 || i >= len(p.routes) {
|
|
||||||
return "", fmt.Errorf("%d is out of range", i)
|
|
||||||
}
|
|
||||||
return p.routes[i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PeerRoutes) Size() int {
|
|
||||||
return len(p.routes)
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package android
|
|
||||||
|
|
||||||
// PlatformFiles groups paths to files used internally by the engine that can't be created/modified
|
|
||||||
// at their default locations due to android OS restrictions.
|
|
||||||
type PlatformFiles interface {
|
|
||||||
ConfigurationFilePath() string
|
|
||||||
StateFilePath() string
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package android
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
|
||||||
"github.com/netbirdio/netbird/route"
|
|
||||||
)
|
|
||||||
|
|
||||||
func executeRouteToggle(id string, manager routemanager.Manager,
|
|
||||||
operationName string,
|
|
||||||
routeOperation func(routes []route.NetID, allRoutes []route.NetID) error) error {
|
|
||||||
netID := route.NetID(id)
|
|
||||||
routes := []route.NetID{netID}
|
|
||||||
|
|
||||||
log.Debugf("%s with id: %s", operationName, id)
|
|
||||||
|
|
||||||
if err := routeOperation(routes, maps.Keys(manager.GetClientRoutesWithNetID())); err != nil {
|
|
||||||
log.Debugf("error when %s: %s", operationName, err)
|
|
||||||
return fmt.Errorf("error %s: %w", operationName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.TriggerSelection(manager.GetClientRoutes())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type routeCommand interface {
|
|
||||||
toggleRoute() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type selectRouteCommand struct {
|
|
||||||
route string
|
|
||||||
manager routemanager.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s selectRouteCommand) toggleRoute() error {
|
|
||||||
routeSelector := s.manager.GetRouteSelector()
|
|
||||||
if routeSelector == nil {
|
|
||||||
return fmt.Errorf("no route selector available")
|
|
||||||
}
|
|
||||||
|
|
||||||
routeOperation := func(routes []route.NetID, allRoutes []route.NetID) error {
|
|
||||||
return routeSelector.SelectRoutes(routes, true, allRoutes)
|
|
||||||
}
|
|
||||||
|
|
||||||
return executeRouteToggle(s.route, s.manager, "selecting route", routeOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
type deselectRouteCommand struct {
|
|
||||||
route string
|
|
||||||
manager routemanager.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d deselectRouteCommand) toggleRoute() error {
|
|
||||||
routeSelector := d.manager.GetRouteSelector()
|
|
||||||
if routeSelector == nil {
|
|
||||||
return fmt.Errorf("no route selector available")
|
|
||||||
}
|
|
||||||
|
|
||||||
return executeRouteToggle(d.route, d.manager, "deselecting route", routeSelector.DeselectRoutes)
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/client/server"
|
"github.com/netbirdio/netbird/client/server"
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/upload-server/types"
|
"github.com/netbirdio/netbird/upload-server/types"
|
||||||
)
|
)
|
||||||
@@ -98,7 +97,6 @@ func debugBundle(cmd *cobra.Command, _ []string) error {
|
|||||||
client := proto.NewDaemonServiceClient(conn)
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
request := &proto.DebugBundleRequest{
|
request := &proto.DebugBundleRequest{
|
||||||
Anonymize: anonymizeFlag,
|
Anonymize: anonymizeFlag,
|
||||||
Status: getStatusOutput(cmd, anonymizeFlag),
|
|
||||||
SystemInfo: systemInfoFlag,
|
SystemInfo: systemInfoFlag,
|
||||||
LogFileCount: logFileCount,
|
LogFileCount: logFileCount,
|
||||||
}
|
}
|
||||||
@@ -220,9 +218,6 @@ func runForDuration(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
headerPostUp := fmt.Sprintf("----- NetBird post-up - Timestamp: %s", time.Now().Format(time.RFC3339))
|
|
||||||
statusOutput := fmt.Sprintf("%s\n%s", headerPostUp, getStatusOutput(cmd, anonymizeFlag))
|
|
||||||
|
|
||||||
if waitErr := waitForDurationOrCancel(cmd.Context(), duration, cmd); waitErr != nil {
|
if waitErr := waitForDurationOrCancel(cmd.Context(), duration, cmd); waitErr != nil {
|
||||||
return waitErr
|
return waitErr
|
||||||
}
|
}
|
||||||
@@ -230,11 +225,8 @@ func runForDuration(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
cmd.Println("Creating debug bundle...")
|
cmd.Println("Creating debug bundle...")
|
||||||
|
|
||||||
headerPreDown := fmt.Sprintf("----- NetBird pre-down - Timestamp: %s - Duration: %s", time.Now().Format(time.RFC3339), duration)
|
|
||||||
statusOutput = fmt.Sprintf("%s\n%s\n%s", statusOutput, headerPreDown, getStatusOutput(cmd, anonymizeFlag))
|
|
||||||
request := &proto.DebugBundleRequest{
|
request := &proto.DebugBundleRequest{
|
||||||
Anonymize: anonymizeFlag,
|
Anonymize: anonymizeFlag,
|
||||||
Status: statusOutput,
|
|
||||||
SystemInfo: systemInfoFlag,
|
SystemInfo: systemInfoFlag,
|
||||||
LogFileCount: logFileCount,
|
LogFileCount: logFileCount,
|
||||||
}
|
}
|
||||||
@@ -301,25 +293,6 @@ func setSyncResponsePersistence(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatusOutput(cmd *cobra.Command, anon bool) string {
|
|
||||||
var statusOutputString string
|
|
||||||
statusResp, err := getStatus(cmd.Context(), true)
|
|
||||||
if err != nil {
|
|
||||||
cmd.PrintErrf("Failed to get status: %v\n", err)
|
|
||||||
} else {
|
|
||||||
pm := profilemanager.NewProfileManager()
|
|
||||||
var profName string
|
|
||||||
if activeProf, err := pm.GetActiveProfile(); err == nil {
|
|
||||||
profName = activeProf.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
statusOutputString = nbstatus.ParseToFullDetailSummary(
|
|
||||||
nbstatus.ConvertToStatusOutputOverview(statusResp, anon, "", nil, nil, nil, "", profName),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return statusOutputString
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForDurationOrCancel(ctx context.Context, duration time.Duration, cmd *cobra.Command) error {
|
func waitForDurationOrCancel(ctx context.Context, duration time.Duration, cmd *cobra.Command) error {
|
||||||
ticker := time.NewTicker(1 * time.Second)
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
@@ -378,7 +351,7 @@ func generateDebugBundle(config *profilemanager.Config, recorder *peer.Status, c
|
|||||||
InternalConfig: config,
|
InternalConfig: config,
|
||||||
StatusRecorder: recorder,
|
StatusRecorder: recorder,
|
||||||
SyncResponse: syncResponse,
|
SyncResponse: syncResponse,
|
||||||
LogFile: logFilePath,
|
LogPath: logFilePath,
|
||||||
},
|
},
|
||||||
debug.BundleConfig{
|
debug.BundleConfig{
|
||||||
IncludeSystemInfo: true,
|
IncludeSystemInfo: true,
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/skratchdot/open-golang/open"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
@@ -330,7 +332,7 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *pro
|
|||||||
hint = profileState.Email
|
hint = profileState.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isUnixRunningDesktop(), false, hint)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isUnixRunningDesktop(), hint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -371,13 +373,21 @@ func openURL(cmd *cobra.Command, verificationURIComplete, userCode string, noBro
|
|||||||
cmd.Println("")
|
cmd.Println("")
|
||||||
|
|
||||||
if !noBrowser {
|
if !noBrowser {
|
||||||
if err := util.OpenBrowser(verificationURIComplete); err != nil {
|
if err := openBrowser(verificationURIComplete); err != nil {
|
||||||
cmd.Println("\nAlternatively, you may want to use a setup key, see:\n\n" +
|
cmd.Println("\nAlternatively, you may want to use a setup key, see:\n\n" +
|
||||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// openBrowser opens the URL in a browser, respecting the BROWSER environment variable.
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
if browser := os.Getenv("BROWSER"); browser != "" {
|
||||||
|
return exec.Command(browser, url).Start()
|
||||||
|
}
|
||||||
|
return open.Run(url)
|
||||||
|
}
|
||||||
|
|
||||||
// isUnixRunningDesktop checks if a Linux OS is running desktop environment
|
// isUnixRunningDesktop checks if a Linux OS is running desktop environment
|
||||||
func isUnixRunningDesktop() bool {
|
func isUnixRunningDesktop() bool {
|
||||||
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
|
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ var (
|
|||||||
identityFile string
|
identityFile string
|
||||||
skipCachedToken bool
|
skipCachedToken bool
|
||||||
requestPTY bool
|
requestPTY bool
|
||||||
sshNoBrowser bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -82,7 +81,6 @@ func init() {
|
|||||||
sshCmd.PersistentFlags().StringVarP(&identityFile, "identity", "i", "", "Path to SSH private key file (deprecated)")
|
sshCmd.PersistentFlags().StringVarP(&identityFile, "identity", "i", "", "Path to SSH private key file (deprecated)")
|
||||||
_ = sshCmd.PersistentFlags().MarkDeprecated("identity", "this flag is no longer used")
|
_ = sshCmd.PersistentFlags().MarkDeprecated("identity", "this flag is no longer used")
|
||||||
sshCmd.PersistentFlags().BoolVar(&skipCachedToken, "no-cache", false, "Skip cached JWT token and force fresh authentication")
|
sshCmd.PersistentFlags().BoolVar(&skipCachedToken, "no-cache", false, "Skip cached JWT token and force fresh authentication")
|
||||||
sshCmd.PersistentFlags().BoolVar(&sshNoBrowser, noBrowserFlag, false, noBrowserDesc)
|
|
||||||
|
|
||||||
sshCmd.PersistentFlags().StringArrayP("L", "L", []string{}, "Local port forwarding [bind_address:]port:host:hostport")
|
sshCmd.PersistentFlags().StringArrayP("L", "L", []string{}, "Local port forwarding [bind_address:]port:host:hostport")
|
||||||
sshCmd.PersistentFlags().StringArrayP("R", "R", []string{}, "Remote port forwarding [bind_address:]port:host:hostport")
|
sshCmd.PersistentFlags().StringArrayP("R", "R", []string{}, "Remote port forwarding [bind_address:]port:host:hostport")
|
||||||
@@ -187,21 +185,6 @@ func getEnvOrDefault(flagName, defaultValue string) string {
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBoolEnvOrDefault checks for boolean environment variables with WT_ and NB_ prefixes
|
|
||||||
func getBoolEnvOrDefault(flagName string, defaultValue bool) bool {
|
|
||||||
if envValue := os.Getenv("WT_" + flagName); envValue != "" {
|
|
||||||
if parsed, err := strconv.ParseBool(envValue); err == nil {
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if envValue := os.Getenv("NB_" + flagName); envValue != "" {
|
|
||||||
if parsed, err := strconv.ParseBool(envValue); err == nil {
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetSSHGlobals sets SSH globals to their default values
|
// resetSSHGlobals sets SSH globals to their default values
|
||||||
func resetSSHGlobals() {
|
func resetSSHGlobals() {
|
||||||
port = sshserver.DefaultSSHPort
|
port = sshserver.DefaultSSHPort
|
||||||
@@ -213,7 +196,6 @@ func resetSSHGlobals() {
|
|||||||
strictHostKeyChecking = true
|
strictHostKeyChecking = true
|
||||||
knownHostsFile = ""
|
knownHostsFile = ""
|
||||||
identityFile = ""
|
identityFile = ""
|
||||||
sshNoBrowser = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseCustomSSHFlags extracts -L, -R flags and returns filtered args
|
// parseCustomSSHFlags extracts -L, -R flags and returns filtered args
|
||||||
@@ -388,7 +370,6 @@ type sshFlags struct {
|
|||||||
KnownHostsFile string
|
KnownHostsFile string
|
||||||
IdentityFile string
|
IdentityFile string
|
||||||
SkipCachedToken bool
|
SkipCachedToken bool
|
||||||
NoBrowser bool
|
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
LocalForwards []string
|
LocalForwards []string
|
||||||
@@ -400,7 +381,6 @@ type sshFlags struct {
|
|||||||
func createSSHFlagSet() (*flag.FlagSet, *sshFlags) {
|
func createSSHFlagSet() (*flag.FlagSet, *sshFlags) {
|
||||||
defaultConfigPath := getEnvOrDefault("CONFIG", configPath)
|
defaultConfigPath := getEnvOrDefault("CONFIG", configPath)
|
||||||
defaultLogLevel := getEnvOrDefault("LOG_LEVEL", logLevel)
|
defaultLogLevel := getEnvOrDefault("LOG_LEVEL", logLevel)
|
||||||
defaultNoBrowser := getBoolEnvOrDefault("NO_BROWSER", false)
|
|
||||||
|
|
||||||
fs := flag.NewFlagSet("ssh-flags", flag.ContinueOnError)
|
fs := flag.NewFlagSet("ssh-flags", flag.ContinueOnError)
|
||||||
fs.SetOutput(nil)
|
fs.SetOutput(nil)
|
||||||
@@ -421,7 +401,6 @@ func createSSHFlagSet() (*flag.FlagSet, *sshFlags) {
|
|||||||
fs.StringVar(&flags.IdentityFile, "i", "", "Path to SSH private key file")
|
fs.StringVar(&flags.IdentityFile, "i", "", "Path to SSH private key file")
|
||||||
fs.StringVar(&flags.IdentityFile, "identity", "", "Path to SSH private key file")
|
fs.StringVar(&flags.IdentityFile, "identity", "", "Path to SSH private key file")
|
||||||
fs.BoolVar(&flags.SkipCachedToken, "no-cache", false, "Skip cached JWT token and force fresh authentication")
|
fs.BoolVar(&flags.SkipCachedToken, "no-cache", false, "Skip cached JWT token and force fresh authentication")
|
||||||
fs.BoolVar(&flags.NoBrowser, "no-browser", defaultNoBrowser, noBrowserDesc)
|
|
||||||
|
|
||||||
fs.StringVar(&flags.ConfigPath, "c", defaultConfigPath, "Netbird config file location")
|
fs.StringVar(&flags.ConfigPath, "c", defaultConfigPath, "Netbird config file location")
|
||||||
fs.StringVar(&flags.ConfigPath, "config", defaultConfigPath, "Netbird config file location")
|
fs.StringVar(&flags.ConfigPath, "config", defaultConfigPath, "Netbird config file location")
|
||||||
@@ -470,7 +449,6 @@ func validateSSHArgsWithoutFlagParsing(_ *cobra.Command, args []string) error {
|
|||||||
knownHostsFile = flags.KnownHostsFile
|
knownHostsFile = flags.KnownHostsFile
|
||||||
identityFile = flags.IdentityFile
|
identityFile = flags.IdentityFile
|
||||||
skipCachedToken = flags.SkipCachedToken
|
skipCachedToken = flags.SkipCachedToken
|
||||||
sshNoBrowser = flags.NoBrowser
|
|
||||||
|
|
||||||
if flags.ConfigPath != getEnvOrDefault("CONFIG", configPath) {
|
if flags.ConfigPath != getEnvOrDefault("CONFIG", configPath) {
|
||||||
configPath = flags.ConfigPath
|
configPath = flags.ConfigPath
|
||||||
@@ -530,7 +508,6 @@ func runSSH(ctx context.Context, addr string, cmd *cobra.Command) error {
|
|||||||
DaemonAddr: daemonAddr,
|
DaemonAddr: daemonAddr,
|
||||||
SkipCachedToken: skipCachedToken,
|
SkipCachedToken: skipCachedToken,
|
||||||
InsecureSkipVerify: !strictHostKeyChecking,
|
InsecureSkipVerify: !strictHostKeyChecking,
|
||||||
NoBrowser: sshNoBrowser,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -772,9 +749,7 @@ func sshProxyFn(cmd *cobra.Command, args []string) error {
|
|||||||
if firstLogFile := util.FindFirstLogPath(logFiles); firstLogFile != "" && firstLogFile != defaultLogFile {
|
if firstLogFile := util.FindFirstLogPath(logFiles); firstLogFile != "" && firstLogFile != defaultLogFile {
|
||||||
logOutput = firstLogFile
|
logOutput = firstLogFile
|
||||||
}
|
}
|
||||||
|
if err := util.InitLog(logLevel, logOutput); err != nil {
|
||||||
proxyLogLevel := getEnvOrDefault("LOG_LEVEL", logLevel)
|
|
||||||
if err := util.InitLog(proxyLogLevel, logOutput); err != nil {
|
|
||||||
return fmt.Errorf("init log: %w", err)
|
return fmt.Errorf("init log: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,15 +761,7 @@ func sshProxyFn(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("invalid port: %s", portStr)
|
return fmt.Errorf("invalid port: %s", portStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check env var for browser setting since this command is invoked via SSH ProxyCommand
|
proxy, err := sshproxy.New(daemonAddr, host, port, cmd.ErrOrStderr())
|
||||||
// where command-line flags cannot be passed. Default is to open browser.
|
|
||||||
noBrowser := getBoolEnvOrDefault("NO_BROWSER", false)
|
|
||||||
var browserOpener func(string) error
|
|
||||||
if !noBrowser {
|
|
||||||
browserOpener = util.OpenBrowser
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy, err := sshproxy.New(daemonAddr, host, port, cmd.ErrOrStderr(), browserOpener)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create SSH proxy: %w", err)
|
return fmt.Errorf("create SSH proxy: %w", err)
|
||||||
}
|
}
|
||||||
@@ -821,8 +788,7 @@ var sshDetectCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sshDetectFn(cmd *cobra.Command, args []string) error {
|
func sshDetectFn(cmd *cobra.Command, args []string) error {
|
||||||
detectLogLevel := getEnvOrDefault("LOG_LEVEL", logLevel)
|
if err := util.InitLog(logLevel, "console"); err != nil {
|
||||||
if err := util.InitLog(detectLogLevel, "console"); err != nil {
|
|
||||||
os.Exit(detection.ServerTypeRegular.ExitCode())
|
os.Exit(detection.ServerTypeRegular.ExitCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,21 +797,15 @@ func sshDetectFn(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("invalid port %q: %v", portStr, err)
|
|
||||||
os.Exit(detection.ServerTypeRegular.ExitCode())
|
os.Exit(detection.ServerTypeRegular.ExitCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(cmd.Context(), detection.DefaultTimeout)
|
dialer := &net.Dialer{Timeout: detection.Timeout}
|
||||||
|
serverType, err := detection.DetectSSHServerType(cmd.Context(), dialer, host, port)
|
||||||
dialer := &net.Dialer{}
|
|
||||||
serverType, err := detection.DetectSSHServerType(ctx, dialer, host, port)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("SSH server detection failed: %v", err)
|
|
||||||
cancel()
|
|
||||||
os.Exit(detection.ServerTypeRegular.ExitCode())
|
os.Exit(detection.ServerTypeRegular.ExitCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
os.Exit(serverType.ExitCode())
|
os.Exit(serverType.ExitCode())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
|
|||||||
profName = activeProf.Name
|
profName = activeProf.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputInformationHolder = nbstatus.ConvertToStatusOutputOverview(resp, anonymizeFlag, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilterMap, connectionTypeFilter, profName)
|
var outputInformationHolder = nbstatus.ConvertToStatusOutputOverview(resp.GetFullStatus(), anonymizeFlag, resp.GetDaemonVersion(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilterMap, connectionTypeFilter, profName)
|
||||||
var statusOutputString string
|
var statusOutputString string
|
||||||
switch {
|
switch {
|
||||||
case detailFlag:
|
case detailFlag:
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers"
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
|
|
||||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
|
|
||||||
clientProto "github.com/netbirdio/netbird/client/proto"
|
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||||
client "github.com/netbirdio/netbird/client/server"
|
client "github.com/netbirdio/netbird/client/server"
|
||||||
@@ -26,6 +25,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/groups"
|
"github.com/netbirdio/netbird/management/server/groups"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
|
"github.com/netbirdio/netbird/management/server/peers"
|
||||||
|
"github.com/netbirdio/netbird/management/server/peers/ephemeral/manager"
|
||||||
"github.com/netbirdio/netbird/management/server/permissions"
|
"github.com/netbirdio/netbird/management/server/permissions"
|
||||||
"github.com/netbirdio/netbird/management/server/settings"
|
"github.com/netbirdio/netbird/management/server/settings"
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
@@ -88,6 +89,7 @@ func startManagement(t *testing.T, config *config.Config, testFile string) (*grp
|
|||||||
}
|
}
|
||||||
t.Cleanup(cleanUp)
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
|
jobManager := job.NewJobManager(nil, store)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -116,18 +118,15 @@ func startManagement(t *testing.T, config *config.Config, testFile string) (*grp
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
updateManager := update_channel.NewPeersUpdateManager(metrics)
|
updateManager := update_channel.NewPeersUpdateManager(metrics)
|
||||||
requestBuffer := mgmt.NewAccountRequestBuffer(ctx, store)
|
requestBuffer := mgmt.NewAccountRequestBuffer(ctx, store)
|
||||||
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, mgmt.MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), manager.NewEphemeralManager(store, peersmanager), config)
|
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, mgmt.MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock())
|
||||||
|
|
||||||
accountManager, err := mgmt.BuildManager(context.Background(), config, store, networkMapController, nil, "", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
accountManager, err := mgmt.BuildManager(context.Background(), config, store, networkMapController, jobManager, nil, "", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsManager, err := nbgrpc.NewTimeBasedAuthSecretsManager(updateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
secretsManager := nbgrpc.NewTimeBasedAuthSecretsManager(updateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
||||||
if err != nil {
|
mgmtServer, err := nbgrpc.NewServer(config, accountManager, settingsMockManager, updateManager, jobManager, secretsManager, nil, &manager.EphemeralManager{}, nil, &mgmt.MockIntegratedValidator{}, networkMapController)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
mgmtServer, err := nbgrpc.NewServer(config, accountManager, settingsMockManager, secretsManager, nil, nil, &mgmt.MockIntegratedValidator{}, networkMapController)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command, activeProf *pr
|
|||||||
connectClient := internal.NewConnectClient(ctx, config, r)
|
connectClient := internal.NewConnectClient(ctx, config, r)
|
||||||
SetupDebugHandler(ctx, config, r, connectClient, "")
|
SetupDebugHandler(ctx, config, r, connectClient, "")
|
||||||
|
|
||||||
return connectClient.Run(nil)
|
return connectClient.Run(nil, util.FindFirstLogPath(logFiles))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInDaemonMode(ctx context.Context, cmd *cobra.Command, pm *profilemanager.ProfileManager, activeProf *profilemanager.Profile, profileSwitched bool) error {
|
func runInDaemonMode(ctx context.Context, cmd *cobra.Command, pm *profilemanager.ProfileManager, activeProf *profilemanager.Profile, profileSwitched bool) error {
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ func (c *Client) Start(startCtx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recorder := peer.NewRecorder(c.config.ManagementURL.String())
|
recorder := peer.NewRecorder(c.config.ManagementURL.String())
|
||||||
|
|
||||||
client := internal.NewConnectClient(ctx, c.config, recorder)
|
client := internal.NewConnectClient(ctx, c.config, recorder)
|
||||||
|
|
||||||
// either startup error (permanent backoff err) or nil err (successful engine up)
|
// either startup error (permanent backoff err) or nil err (successful engine up)
|
||||||
@@ -180,7 +181,7 @@ func (c *Client) Start(startCtx context.Context) error {
|
|||||||
run := make(chan struct{})
|
run := make(chan struct{})
|
||||||
clientErr := make(chan error, 1)
|
clientErr := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
if err := client.Run(run); err != nil {
|
if err := client.Run(run, ""); err != nil {
|
||||||
clientErr <- err
|
clientErr <- err
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -27,11 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tableNat = "nat"
|
tableNat = "nat"
|
||||||
tableMangle = "mangle"
|
|
||||||
tableRaw = "raw"
|
|
||||||
tableSecurity = "security"
|
|
||||||
|
|
||||||
chainNameNatPrerouting = "PREROUTING"
|
chainNameNatPrerouting = "PREROUTING"
|
||||||
chainNameRoutingFw = "netbird-rt-fwd"
|
chainNameRoutingFw = "netbird-rt-fwd"
|
||||||
chainNameRoutingNat = "netbird-rt-postrouting"
|
chainNameRoutingNat = "netbird-rt-postrouting"
|
||||||
@@ -95,7 +91,11 @@ func newRouter(workTable *nftables.Table, wgIface iFaceMapper, mtu uint16) (*rou
|
|||||||
var err error
|
var err error
|
||||||
r.filterTable, err = r.loadFilterTable()
|
r.filterTable, err = r.loadFilterTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("ip filter table not found: %v", err)
|
if errors.Is(err, errFilterTableNotFound) {
|
||||||
|
log.Warnf("table 'filter' not found for forward rules")
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("load filter table: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
@@ -175,7 +175,7 @@ func (r *router) removeNatPreroutingRules() error {
|
|||||||
func (r *router) loadFilterTable() (*nftables.Table, error) {
|
func (r *router) loadFilterTable() (*nftables.Table, error) {
|
||||||
tables, err := r.conn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
tables, err := r.conn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list tables: %w", err)
|
return nil, fmt.Errorf("unable to list tables: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
@@ -187,39 +187,14 @@ func (r *router) loadFilterTable() (*nftables.Table, error) {
|
|||||||
return nil, errFilterTableNotFound
|
return nil, errFilterTableNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func hookName(hook *nftables.ChainHook) string {
|
|
||||||
if hook == nil {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
switch *hook {
|
|
||||||
case *nftables.ChainHookForward:
|
|
||||||
return chainNameForward
|
|
||||||
case *nftables.ChainHookInput:
|
|
||||||
return chainNameInput
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("hook(%d)", *hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func familyName(family nftables.TableFamily) string {
|
|
||||||
switch family {
|
|
||||||
case nftables.TableFamilyIPv4:
|
|
||||||
return "ip"
|
|
||||||
case nftables.TableFamilyIPv6:
|
|
||||||
return "ip6"
|
|
||||||
case nftables.TableFamilyINet:
|
|
||||||
return "inet"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("family(%d)", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *router) createContainers() error {
|
func (r *router) createContainers() error {
|
||||||
r.chains[chainNameRoutingFw] = r.conn.AddChain(&nftables.Chain{
|
r.chains[chainNameRoutingFw] = r.conn.AddChain(&nftables.Chain{
|
||||||
Name: chainNameRoutingFw,
|
Name: chainNameRoutingFw,
|
||||||
Table: r.workTable,
|
Table: r.workTable,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
insertReturnTrafficRule(r.conn, r.workTable, r.chains[chainNameRoutingFw])
|
||||||
|
|
||||||
prio := *nftables.ChainPriorityNATSource - 1
|
prio := *nftables.ChainPriorityNATSource - 1
|
||||||
r.chains[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{
|
r.chains[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{
|
||||||
Name: chainNameRoutingNat,
|
Name: chainNameRoutingNat,
|
||||||
@@ -261,12 +236,9 @@ func (r *router) createContainers() error {
|
|||||||
Type: nftables.ChainTypeFilter,
|
Type: nftables.ChainTypeFilter,
|
||||||
})
|
})
|
||||||
|
|
||||||
insertReturnTrafficRule(r.conn, r.workTable, r.chains[chainNameRoutingFw])
|
// Add the single NAT rule that matches on mark
|
||||||
|
if err := r.addPostroutingRules(); err != nil {
|
||||||
r.addPostroutingRules()
|
return fmt.Errorf("add single nat rule: %v", err)
|
||||||
|
|
||||||
if err := r.conn.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("initialize tables: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.addMSSClampingRules(); err != nil {
|
if err := r.addMSSClampingRules(); err != nil {
|
||||||
@@ -278,7 +250,11 @@ func (r *router) createContainers() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := r.refreshRulesMap(); err != nil {
|
if err := r.refreshRulesMap(); err != nil {
|
||||||
log.Errorf("failed to refresh rules: %s", err)
|
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.conn.Flush(); err != nil {
|
||||||
|
return fmt.Errorf("initialize tables: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -719,7 +695,7 @@ func (r *router) addNatRule(pair firewall.RouterPair) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addPostroutingRules adds the masquerade rules
|
// addPostroutingRules adds the masquerade rules
|
||||||
func (r *router) addPostroutingRules() {
|
func (r *router) addPostroutingRules() error {
|
||||||
// First masquerade rule for traffic coming in from WireGuard interface
|
// First masquerade rule for traffic coming in from WireGuard interface
|
||||||
exprs := []expr.Any{
|
exprs := []expr.Any{
|
||||||
// Match on the first fwmark
|
// Match on the first fwmark
|
||||||
@@ -785,6 +761,8 @@ func (r *router) addPostroutingRules() {
|
|||||||
Chain: r.chains[chainNameRoutingNat],
|
Chain: r.chains[chainNameRoutingNat],
|
||||||
Exprs: exprs2,
|
Exprs: exprs2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addMSSClampingRules adds MSS clamping rules to prevent fragmentation for forwarded traffic.
|
// addMSSClampingRules adds MSS clamping rules to prevent fragmentation for forwarded traffic.
|
||||||
@@ -861,7 +839,7 @@ func (r *router) addMSSClampingRules() error {
|
|||||||
Exprs: exprsOut,
|
Exprs: exprsOut,
|
||||||
})
|
})
|
||||||
|
|
||||||
return r.conn.Flush()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addLegacyRouteRule adds a legacy routing rule for mgmt servers pre route acls
|
// addLegacyRouteRule adds a legacy routing rule for mgmt servers pre route acls
|
||||||
@@ -961,21 +939,8 @@ func (r *router) RemoveAllLegacyRouteRules() error {
|
|||||||
// In case the FORWARD policy is set to "drop", we add an established/related rule to allow return traffic for the inbound rule.
|
// In case the FORWARD policy is set to "drop", we add an established/related rule to allow return traffic for the inbound rule.
|
||||||
// This method also adds INPUT chain rules to allow traffic to the local interface.
|
// This method also adds INPUT chain rules to allow traffic to the local interface.
|
||||||
func (r *router) acceptForwardRules() error {
|
func (r *router) acceptForwardRules() error {
|
||||||
var merr *multierror.Error
|
|
||||||
|
|
||||||
if err := r.acceptFilterTableRules(); err != nil {
|
|
||||||
merr = multierror.Append(merr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.acceptExternalChainsRules(); err != nil {
|
|
||||||
merr = multierror.Append(merr, fmt.Errorf("add accept rules to external chains: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nberrors.FormatErrorOrNil(merr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *router) acceptFilterTableRules() error {
|
|
||||||
if r.filterTable == nil {
|
if r.filterTable == nil {
|
||||||
|
log.Debugf("table 'filter' not found for forward rules, skipping accept rules")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,11 +953,11 @@ func (r *router) acceptFilterTableRules() error {
|
|||||||
// Try iptables first and fallback to nftables if iptables is not available
|
// Try iptables first and fallback to nftables if iptables is not available
|
||||||
ipt, err := iptables.New()
|
ipt, err := iptables.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// iptables is not available but the filter table exists
|
// filter table exists but iptables is not
|
||||||
log.Warnf("Will use nftables to manipulate the filter table because iptables is not available: %v", err)
|
log.Warnf("Will use nftables to manipulate the filter table because iptables is not available: %v", err)
|
||||||
|
|
||||||
fw = "nftables"
|
fw = "nftables"
|
||||||
return r.acceptFilterRulesNftables(r.filterTable)
|
return r.acceptFilterRulesNftables()
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.acceptFilterRulesIptables(ipt)
|
return r.acceptFilterRulesIptables(ipt)
|
||||||
@@ -1003,7 +968,7 @@ func (r *router) acceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
|||||||
|
|
||||||
for _, rule := range r.getAcceptForwardRules() {
|
for _, rule := range r.getAcceptForwardRules() {
|
||||||
if err := ipt.Insert("filter", chainNameForward, 1, rule...); err != nil {
|
if err := ipt.Insert("filter", chainNameForward, 1, rule...); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("add iptables forward rule: %v", err))
|
merr = multierror.Append(err, fmt.Errorf("add iptables forward rule: %v", err))
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("added iptables forward rule: %v", rule)
|
log.Debugf("added iptables forward rule: %v", rule)
|
||||||
}
|
}
|
||||||
@@ -1011,7 +976,7 @@ func (r *router) acceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
|||||||
|
|
||||||
inputRule := r.getAcceptInputRule()
|
inputRule := r.getAcceptInputRule()
|
||||||
if err := ipt.Insert("filter", chainNameInput, 1, inputRule...); err != nil {
|
if err := ipt.Insert("filter", chainNameInput, 1, inputRule...); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("add iptables input rule: %v", err))
|
merr = multierror.Append(err, fmt.Errorf("add iptables input rule: %v", err))
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("added iptables input rule: %v", inputRule)
|
log.Debugf("added iptables input rule: %v", inputRule)
|
||||||
}
|
}
|
||||||
@@ -1031,70 +996,18 @@ func (r *router) getAcceptInputRule() []string {
|
|||||||
return []string{"-i", r.wgIface.Name(), "-j", "ACCEPT"}
|
return []string{"-i", r.wgIface.Name(), "-j", "ACCEPT"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// acceptFilterRulesNftables adds accept rules to the ip filter table using nftables.
|
func (r *router) acceptFilterRulesNftables() error {
|
||||||
// This is used when iptables is not available.
|
|
||||||
func (r *router) acceptFilterRulesNftables(table *nftables.Table) error {
|
|
||||||
intf := ifname(r.wgIface.Name())
|
intf := ifname(r.wgIface.Name())
|
||||||
|
|
||||||
forwardChain := &nftables.Chain{
|
|
||||||
Name: chainNameForward,
|
|
||||||
Table: table,
|
|
||||||
Type: nftables.ChainTypeFilter,
|
|
||||||
Hooknum: nftables.ChainHookForward,
|
|
||||||
Priority: nftables.ChainPriorityFilter,
|
|
||||||
}
|
|
||||||
r.insertForwardAcceptRules(forwardChain, intf)
|
|
||||||
|
|
||||||
inputChain := &nftables.Chain{
|
|
||||||
Name: chainNameInput,
|
|
||||||
Table: table,
|
|
||||||
Type: nftables.ChainTypeFilter,
|
|
||||||
Hooknum: nftables.ChainHookInput,
|
|
||||||
Priority: nftables.ChainPriorityFilter,
|
|
||||||
}
|
|
||||||
r.insertInputAcceptRule(inputChain, intf)
|
|
||||||
|
|
||||||
return r.conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptExternalChainsRules adds accept rules to external chains (non-netbird, non-iptables tables).
|
|
||||||
// It dynamically finds chains at call time to handle chains that may have been created after startup.
|
|
||||||
func (r *router) acceptExternalChainsRules() error {
|
|
||||||
chains := r.findExternalChains()
|
|
||||||
if len(chains) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
intf := ifname(r.wgIface.Name())
|
|
||||||
|
|
||||||
for _, chain := range chains {
|
|
||||||
if chain.Hooknum == nil {
|
|
||||||
log.Debugf("skipping external chain %s/%s: hooknum is nil", chain.Table.Name, chain.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("adding accept rules to external %s chain: %s %s/%s",
|
|
||||||
hookName(chain.Hooknum), familyName(chain.Table.Family), chain.Table.Name, chain.Name)
|
|
||||||
|
|
||||||
switch *chain.Hooknum {
|
|
||||||
case *nftables.ChainHookForward:
|
|
||||||
r.insertForwardAcceptRules(chain, intf)
|
|
||||||
case *nftables.ChainHookInput:
|
|
||||||
r.insertInputAcceptRule(chain, intf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.conn.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("flush external chain rules: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *router) insertForwardAcceptRules(chain *nftables.Chain, intf []byte) {
|
|
||||||
iifRule := &nftables.Rule{
|
iifRule := &nftables.Rule{
|
||||||
Table: chain.Table,
|
Table: r.filterTable,
|
||||||
Chain: chain,
|
Chain: &nftables.Chain{
|
||||||
|
Name: chainNameForward,
|
||||||
|
Table: r.filterTable,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Priority: nftables.ChainPriorityFilter,
|
||||||
|
},
|
||||||
Exprs: []expr.Any{
|
Exprs: []expr.Any{
|
||||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||||
&expr.Cmp{
|
&expr.Cmp{
|
||||||
@@ -1117,19 +1030,30 @@ func (r *router) insertForwardAcceptRules(chain *nftables.Chain, intf []byte) {
|
|||||||
Data: intf,
|
Data: intf,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
oifRule := &nftables.Rule{
|
oifRule := &nftables.Rule{
|
||||||
Table: chain.Table,
|
Table: r.filterTable,
|
||||||
Chain: chain,
|
Chain: &nftables.Chain{
|
||||||
|
Name: chainNameForward,
|
||||||
|
Table: r.filterTable,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Priority: nftables.ChainPriorityFilter,
|
||||||
|
},
|
||||||
Exprs: append(oifExprs, getEstablishedExprs(2)...),
|
Exprs: append(oifExprs, getEstablishedExprs(2)...),
|
||||||
UserData: []byte(userDataAcceptForwardRuleOif),
|
UserData: []byte(userDataAcceptForwardRuleOif),
|
||||||
}
|
}
|
||||||
r.conn.InsertRule(oifRule)
|
r.conn.InsertRule(oifRule)
|
||||||
}
|
|
||||||
|
|
||||||
func (r *router) insertInputAcceptRule(chain *nftables.Chain, intf []byte) {
|
|
||||||
inputRule := &nftables.Rule{
|
inputRule := &nftables.Rule{
|
||||||
Table: chain.Table,
|
Table: r.filterTable,
|
||||||
Chain: chain,
|
Chain: &nftables.Chain{
|
||||||
|
Name: chainNameInput,
|
||||||
|
Table: r.filterTable,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
Hooknum: nftables.ChainHookInput,
|
||||||
|
Priority: nftables.ChainPriorityFilter,
|
||||||
|
},
|
||||||
Exprs: []expr.Any{
|
Exprs: []expr.Any{
|
||||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||||
&expr.Cmp{
|
&expr.Cmp{
|
||||||
@@ -1143,44 +1067,32 @@ func (r *router) insertInputAcceptRule(chain *nftables.Chain, intf []byte) {
|
|||||||
UserData: []byte(userDataAcceptInputRule),
|
UserData: []byte(userDataAcceptInputRule),
|
||||||
}
|
}
|
||||||
r.conn.InsertRule(inputRule)
|
r.conn.InsertRule(inputRule)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) removeAcceptFilterRules() error {
|
func (r *router) removeAcceptFilterRules() error {
|
||||||
var merr *multierror.Error
|
|
||||||
|
|
||||||
if err := r.removeFilterTableRules(); err != nil {
|
|
||||||
merr = multierror.Append(merr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.removeExternalChainsRules(); err != nil {
|
|
||||||
merr = multierror.Append(merr, fmt.Errorf("remove external chain rules: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nberrors.FormatErrorOrNil(merr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *router) removeFilterTableRules() error {
|
|
||||||
if r.filterTable == nil {
|
if r.filterTable == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ipt, err := iptables.New()
|
ipt, err := iptables.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("iptables not available, using nftables to remove filter rules: %v", err)
|
log.Warnf("Will use nftables to manipulate the filter table because iptables is not available: %v", err)
|
||||||
return r.removeAcceptRulesFromTable(r.filterTable)
|
return r.removeAcceptFilterRulesNftables()
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.removeAcceptFilterRulesIptables(ipt)
|
return r.removeAcceptFilterRulesIptables(ipt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) removeAcceptRulesFromTable(table *nftables.Table) error {
|
func (r *router) removeAcceptFilterRulesNftables() error {
|
||||||
chains, err := r.conn.ListChainsOfTableFamily(table.Family)
|
chains, err := r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list chains: %v", err)
|
return fmt.Errorf("list chains: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chain := range chains {
|
for _, chain := range chains {
|
||||||
if chain.Table.Name != table.Name {
|
if chain.Table.Name != r.filterTable.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1188,101 +1100,27 @@ func (r *router) removeAcceptRulesFromTable(table *nftables.Table) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.removeAcceptRulesFromChain(table, chain); err != nil {
|
rules, err := r.conn.GetRules(r.filterTable, chain)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *router) removeAcceptRulesFromChain(table *nftables.Table, chain *nftables.Chain) error {
|
|
||||||
rules, err := r.conn.GetRules(table, chain)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get rules from %s/%s: %v", table.Name, chain.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range rules {
|
|
||||||
if bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleIif)) ||
|
|
||||||
bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleOif)) ||
|
|
||||||
bytes.Equal(rule.UserData, []byte(userDataAcceptInputRule)) {
|
|
||||||
if err := r.conn.DelRule(rule); err != nil {
|
|
||||||
return fmt.Errorf("delete rule from %s/%s: %v", table.Name, chain.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeExternalChainsRules removes our accept rules from all external chains.
|
|
||||||
// This is deterministic - it scans for chains at removal time rather than relying on saved state,
|
|
||||||
// ensuring cleanup works even after a crash or if chains changed.
|
|
||||||
func (r *router) removeExternalChainsRules() error {
|
|
||||||
chains := r.findExternalChains()
|
|
||||||
if len(chains) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chain := range chains {
|
|
||||||
if err := r.removeAcceptRulesFromChain(chain.Table, chain); err != nil {
|
|
||||||
log.Warnf("remove rules from external chain %s/%s: %v", chain.Table.Name, chain.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// findExternalChains scans for chains from non-netbird tables that have FORWARD or INPUT hooks.
|
|
||||||
// This is used both at startup (to know where to add rules) and at cleanup (to ensure deterministic removal).
|
|
||||||
func (r *router) findExternalChains() []*nftables.Chain {
|
|
||||||
var chains []*nftables.Chain
|
|
||||||
|
|
||||||
families := []nftables.TableFamily{nftables.TableFamilyIPv4, nftables.TableFamilyINet}
|
|
||||||
|
|
||||||
for _, family := range families {
|
|
||||||
allChains, err := r.conn.ListChainsOfTableFamily(family)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("list chains for family %d: %v", family, err)
|
return fmt.Errorf("get rules: %v", err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chain := range allChains {
|
for _, rule := range rules {
|
||||||
if r.isExternalChain(chain) {
|
if bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleIif)) ||
|
||||||
chains = append(chains, chain)
|
bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleOif)) ||
|
||||||
|
bytes.Equal(rule.UserData, []byte(userDataAcceptInputRule)) {
|
||||||
|
if err := r.conn.DelRule(rule); err != nil {
|
||||||
|
return fmt.Errorf("delete rule: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chains
|
if err := r.conn.Flush(); err != nil {
|
||||||
}
|
return fmt.Errorf(flushError, err)
|
||||||
|
|
||||||
func (r *router) isExternalChain(chain *nftables.Chain) bool {
|
|
||||||
if r.workTable != nil && chain.Table.Name == r.workTable.Name {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip all iptables-managed tables in the ip family
|
return nil
|
||||||
if chain.Table.Family == nftables.TableFamilyIPv4 && isIptablesTable(chain.Table.Name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if chain.Type != nftables.ChainTypeFilter {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if chain.Hooknum == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return *chain.Hooknum == *nftables.ChainHookForward || *chain.Hooknum == *nftables.ChainHookInput
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIptablesTable(name string) bool {
|
|
||||||
switch name {
|
|
||||||
case tableNameFilter, tableNat, tableMangle, tableRaw, tableSecurity:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) removeAcceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
func (r *router) removeAcceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
||||||
@@ -1290,13 +1128,13 @@ func (r *router) removeAcceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
|||||||
|
|
||||||
for _, rule := range r.getAcceptForwardRules() {
|
for _, rule := range r.getAcceptForwardRules() {
|
||||||
if err := ipt.DeleteIfExists("filter", chainNameForward, rule...); err != nil {
|
if err := ipt.DeleteIfExists("filter", chainNameForward, rule...); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("remove iptables forward rule: %v", err))
|
merr = multierror.Append(err, fmt.Errorf("remove iptables forward rule: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputRule := r.getAcceptInputRule()
|
inputRule := r.getAcceptInputRule()
|
||||||
if err := ipt.DeleteIfExists("filter", chainNameInput, inputRule...); err != nil {
|
if err := ipt.DeleteIfExists("filter", chainNameInput, inputRule...); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("remove iptables input rule: %v", err))
|
merr = multierror.Append(err, fmt.Errorf("remove iptables input rule: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nberrors.FormatErrorOrNil(merr)
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
@@ -1358,7 +1196,7 @@ func (r *router) refreshRulesMap() error {
|
|||||||
for _, chain := range r.chains {
|
for _, chain := range r.chains {
|
||||||
rules, err := r.conn.GetRules(chain.Table, chain)
|
rules, err := r.conn.GetRules(chain.Table, chain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list rules: %w", err)
|
return fmt.Errorf(" unable to list rules: %v", err)
|
||||||
}
|
}
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if len(rule.UserData) > 0 {
|
if len(rule.UserData) > 0 {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -20,12 +19,11 @@ import (
|
|||||||
|
|
||||||
// WGTunDevice ignore the WGTunDevice interface on Android because the creation of the tun device is different on this platform
|
// WGTunDevice ignore the WGTunDevice interface on Android because the creation of the tun device is different on this platform
|
||||||
type WGTunDevice struct {
|
type WGTunDevice struct {
|
||||||
address wgaddr.Address
|
address wgaddr.Address
|
||||||
port int
|
port int
|
||||||
key string
|
key string
|
||||||
mtu uint16
|
mtu uint16
|
||||||
iceBind *bind.ICEBind
|
iceBind *bind.ICEBind
|
||||||
// todo: review if we can eliminate the TunAdapter
|
|
||||||
tunAdapter TunAdapter
|
tunAdapter TunAdapter
|
||||||
disableDNS bool
|
disableDNS bool
|
||||||
|
|
||||||
@@ -34,19 +32,17 @@ type WGTunDevice struct {
|
|||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
udpMux *udpmux.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
renewableTun *RenewableTUN
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTunDevice(address wgaddr.Address, port int, key string, mtu uint16, iceBind *bind.ICEBind, tunAdapter TunAdapter, disableDNS bool) *WGTunDevice {
|
func NewTunDevice(address wgaddr.Address, port int, key string, mtu uint16, iceBind *bind.ICEBind, tunAdapter TunAdapter, disableDNS bool) *WGTunDevice {
|
||||||
return &WGTunDevice{
|
return &WGTunDevice{
|
||||||
address: address,
|
address: address,
|
||||||
port: port,
|
port: port,
|
||||||
key: key,
|
key: key,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
iceBind: iceBind,
|
iceBind: iceBind,
|
||||||
tunAdapter: tunAdapter,
|
tunAdapter: tunAdapter,
|
||||||
disableDNS: disableDNS,
|
disableDNS: disableDNS,
|
||||||
renewableTun: NewRenewableTUN(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,17 +65,14 @@ func (t *WGTunDevice) Create(routes []string, dns string, searchDomains []string
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unmonitoredTUN, name, err := tun.CreateUnmonitoredTUNFromFD(fd)
|
tunDevice, name, err := tun.CreateUnmonitoredTUNFromFD(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = unix.Close(fd)
|
_ = unix.Close(fd)
|
||||||
log.Errorf("failed to create Android interface: %s", err)
|
log.Errorf("failed to create Android interface: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.renewableTun.AddDevice(unmonitoredTUN)
|
|
||||||
|
|
||||||
t.name = name
|
t.name = name
|
||||||
t.filteredDevice = newDeviceFilter(t.renewableTun)
|
t.filteredDevice = newDeviceFilter(tunDevice)
|
||||||
|
|
||||||
log.Debugf("attaching to interface %v", name)
|
log.Debugf("attaching to interface %v", name)
|
||||||
t.device = device.NewDevice(t.filteredDevice, t.iceBind, device.NewLogger(wgLogLevel(), "[netbird] "))
|
t.device = device.NewDevice(t.filteredDevice, t.iceBind, device.NewLogger(wgLogLevel(), "[netbird] "))
|
||||||
@@ -111,23 +104,6 @@ func (t *WGTunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
|||||||
return udpMux, nil
|
return udpMux, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *WGTunDevice) RenewTun(fd int) error {
|
|
||||||
if t.device == nil {
|
|
||||||
return fmt.Errorf("device not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
unmonitoredTUN, _, err := tun.CreateUnmonitoredTUNFromFD(fd)
|
|
||||||
if err != nil {
|
|
||||||
_ = unix.Close(fd)
|
|
||||||
log.Errorf("failed to renew Android interface: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.renewableTun.AddDevice(unmonitoredTUN)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *WGTunDevice) UpdateAddr(addr wgaddr.Address) error {
|
func (t *WGTunDevice) UpdateAddr(addr wgaddr.Address) error {
|
||||||
// todo implement
|
// todo implement
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func (t *TunNetstackDevice) Create(routes []string, dns string, searchDomains []string) (WGConfigurer, error) {
|
func (t *TunNetstackDevice) Create(routes []string, dns string, searchDomains []string) (WGConfigurer, error) {
|
||||||
return t.create()
|
return t.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunNetstackDevice) RenewTun(fd int) error {
|
|
||||||
// Doesn't make sense in Android for Netstack.
|
|
||||||
return fmt.Errorf("this function has not been implemented in Netstack for Android")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,309 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
|
||||||
|
|
||||||
// closeAwareDevice wraps a tun.Device along with a flag
|
|
||||||
// indicating whether its Close method was called.
|
|
||||||
//
|
|
||||||
// It also redirects tun.Device's Events() to a separate goroutine
|
|
||||||
// and closes it when Close is called.
|
|
||||||
//
|
|
||||||
// The WaitGroup and CloseOnce fields are used to ensure that the
|
|
||||||
// goroutine is awaited and closed only once.
|
|
||||||
type closeAwareDevice struct {
|
|
||||||
isClosed atomic.Bool
|
|
||||||
tun.Device
|
|
||||||
closeEventCh chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
closeOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClosableDevice(tunDevice tun.Device) *closeAwareDevice {
|
|
||||||
return &closeAwareDevice{
|
|
||||||
Device: tunDevice,
|
|
||||||
isClosed: atomic.Bool{},
|
|
||||||
closeEventCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirectEvents redirects the Events() method of the underlying tun.Device
|
|
||||||
// to the given channel (RenewableTUN's events channel).
|
|
||||||
func (c *closeAwareDevice) redirectEvents(out chan tun.Event) {
|
|
||||||
c.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer c.wg.Done()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev, ok := <-c.Device.Events():
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ev == tun.EventDown {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case out <- ev:
|
|
||||||
case <-c.closeEventCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-c.closeEventCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close calls the underlying Device's Close method
|
|
||||||
// after setting isClosed to true.
|
|
||||||
func (c *closeAwareDevice) Close() (err error) {
|
|
||||||
c.closeOnce.Do(func() {
|
|
||||||
c.isClosed.Store(true)
|
|
||||||
close(c.closeEventCh)
|
|
||||||
err = c.Device.Close()
|
|
||||||
c.wg.Wait()
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *closeAwareDevice) IsClosed() bool {
|
|
||||||
return c.isClosed.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
type RenewableTUN struct {
|
|
||||||
devices []*closeAwareDevice
|
|
||||||
mu sync.Mutex
|
|
||||||
cond *sync.Cond
|
|
||||||
events chan tun.Event
|
|
||||||
closed atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRenewableTUN() *RenewableTUN {
|
|
||||||
r := &RenewableTUN{
|
|
||||||
devices: make([]*closeAwareDevice, 0),
|
|
||||||
mu: sync.Mutex{},
|
|
||||||
events: make(chan tun.Event, 16),
|
|
||||||
}
|
|
||||||
r.cond = sync.NewCond(&r.mu)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) File() *os.File {
|
|
||||||
for {
|
|
||||||
dev := r.peekLast()
|
|
||||||
if dev == nil {
|
|
||||||
if !r.waitForDevice() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
file := dev.File()
|
|
||||||
|
|
||||||
if dev.IsClosed() {
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads from an underlying tun.Device kept in the r.devices slice.
|
|
||||||
// If no device is available, it waits for one to be added via AddDevice().
|
|
||||||
//
|
|
||||||
// On error, it retries reading from the newest device instead of returning the error
|
|
||||||
// if the device is closed; if not, it propagates the error.
|
|
||||||
func (r *RenewableTUN) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
|
||||||
for {
|
|
||||||
dev := r.peekLast()
|
|
||||||
if dev == nil {
|
|
||||||
// wait until AddDevice() signals a new device via cond.Broadcast()
|
|
||||||
if !r.waitForDevice() { // returns false if the renewable TUN itself is closed
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = dev.Read(bufs, sizes, offset)
|
|
||||||
if err == nil {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap in progress; retry on the newest instead of returning the error
|
|
||||||
if dev.IsClosed() {
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return n, err // propagate non-swap error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes to underlying tun.Device kept in the r.devices slice.
|
|
||||||
// If no device is available, it waits for one to be added via AddDevice().
|
|
||||||
//
|
|
||||||
// On error, it retries writing to the newest device instead of returning the error
|
|
||||||
// if the device is closed; if not, it propagates the error.
|
|
||||||
func (r *RenewableTUN) Write(bufs [][]byte, offset int) (int, error) {
|
|
||||||
for {
|
|
||||||
dev := r.peekLast()
|
|
||||||
if dev == nil {
|
|
||||||
if !r.waitForDevice() {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := dev.Write(bufs, offset)
|
|
||||||
if err == nil {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if dev.IsClosed() {
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) MTU() (int, error) {
|
|
||||||
for {
|
|
||||||
dev := r.peekLast()
|
|
||||||
if dev == nil {
|
|
||||||
if !r.waitForDevice() {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
mtu, err := dev.MTU()
|
|
||||||
if err == nil {
|
|
||||||
return mtu, nil
|
|
||||||
}
|
|
||||||
if dev.IsClosed() {
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) Name() (string, error) {
|
|
||||||
for {
|
|
||||||
dev := r.peekLast()
|
|
||||||
if dev == nil {
|
|
||||||
if !r.waitForDevice() {
|
|
||||||
return "", io.EOF
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, err := dev.Name()
|
|
||||||
if err == nil {
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
if dev.IsClosed() {
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events returns a channel that is fed events from the underlying tun.Device's events channel
|
|
||||||
// once it is added.
|
|
||||||
func (r *RenewableTUN) Events() <-chan tun.Event {
|
|
||||||
return r.events
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) Close() error {
|
|
||||||
// Attempts to set the RenewableTUN closed flag to true.
|
|
||||||
// If it's already true, returns immediately.
|
|
||||||
if !r.closed.CompareAndSwap(false, true) {
|
|
||||||
return nil // already closed: idempotent
|
|
||||||
}
|
|
||||||
r.mu.Lock()
|
|
||||||
devices := r.devices
|
|
||||||
r.devices = nil
|
|
||||||
r.cond.Broadcast()
|
|
||||||
r.mu.Unlock()
|
|
||||||
|
|
||||||
var lastErr error
|
|
||||||
|
|
||||||
log.Debugf("closing %d devices", len(devices))
|
|
||||||
for _, device := range devices {
|
|
||||||
if err := device.Close(); err != nil {
|
|
||||||
log.Debugf("error closing a device: %v", err)
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(r.events)
|
|
||||||
return lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) BatchSize() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) AddDevice(device tun.Device) {
|
|
||||||
r.mu.Lock()
|
|
||||||
if r.closed.Load() {
|
|
||||||
r.mu.Unlock()
|
|
||||||
_ = device.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var toClose *closeAwareDevice
|
|
||||||
if len(r.devices) > 0 {
|
|
||||||
toClose = r.devices[len(r.devices)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
cad := newClosableDevice(device)
|
|
||||||
cad.redirectEvents(r.events)
|
|
||||||
|
|
||||||
r.devices = []*closeAwareDevice{cad}
|
|
||||||
r.cond.Broadcast()
|
|
||||||
|
|
||||||
r.mu.Unlock()
|
|
||||||
|
|
||||||
if toClose != nil {
|
|
||||||
if err := toClose.Close(); err != nil {
|
|
||||||
log.Debugf("error closing last device: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) waitForDevice() bool {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
for len(r.devices) == 0 && !r.closed.Load() {
|
|
||||||
r.cond.Wait()
|
|
||||||
}
|
|
||||||
return !r.closed.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RenewableTUN) peekLast() *closeAwareDevice {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
if len(r.devices) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.devices[len(r.devices)-1]
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,5 @@ type WGTunDevice interface {
|
|||||||
FilteredDevice() *device.FilteredDevice
|
FilteredDevice() *device.FilteredDevice
|
||||||
Device() *wgdevice.Device
|
Device() *wgdevice.Device
|
||||||
GetNet() *netstack.Net
|
GetNet() *netstack.Net
|
||||||
RenewTun(fd int) error
|
|
||||||
GetICEBind() device.EndpointManager
|
GetICEBind() device.EndpointManager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,3 @@ func (w *WGIface) Create() error {
|
|||||||
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
||||||
return fmt.Errorf("this function has not implemented on non mobile")
|
return fmt.Errorf("this function has not implemented on non mobile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WGIface) RenewTun(fd int) error {
|
|
||||||
return fmt.Errorf("this function has not been implemented on non-android")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up.
|
// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
// Will reuse an existing one.
|
// Will reuse an existing one.
|
||||||
// todo: review does this function really necessary or can we merge it with iOS
|
|
||||||
func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []string) error {
|
func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []string) error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
@@ -23,9 +22,3 @@ func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []s
|
|||||||
func (w *WGIface) Create() error {
|
func (w *WGIface) Create() error {
|
||||||
return fmt.Errorf("this function has not implemented on this platform")
|
return fmt.Errorf("this function has not implemented on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WGIface) RenewTun(fd int) error {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
return w.tun.RenewTun(fd)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,7 +39,3 @@ func (w *WGIface) Create() error {
|
|||||||
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
||||||
return fmt.Errorf("this function has not implemented on this platform")
|
return fmt.Errorf("this function has not implemented on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WGIface) RenewTun(fd int) error {
|
|
||||||
return fmt.Errorf("this function has not been implemented on this platform")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -60,19 +60,14 @@ func (t TokenInfo) GetTokenToUse() string {
|
|||||||
return t.AccessToken
|
return t.AccessToken
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldUseDeviceFlow(force bool, isUnixDesktopClient bool) bool {
|
|
||||||
return force || (runtime.GOOS == "linux" || runtime.GOOS == "freebsd") && !isUnixDesktopClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration
|
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration
|
||||||
//
|
//
|
||||||
// It starts by initializing the PKCE.If this process fails, it resorts to the Device Code Flow,
|
// It starts by initializing the PKCE.If this process fails, it resorts to the Device Code Flow,
|
||||||
// and if that also fails, the authentication process is deemed unsuccessful
|
// and if that also fails, the authentication process is deemed unsuccessful
|
||||||
//
|
//
|
||||||
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
|
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
|
||||||
// forceDeviceCodeFlow can be used to skip PKCE and go directly to Device Code Flow (e.g., for Android TV)
|
func NewOAuthFlow(ctx context.Context, config *profilemanager.Config, isUnixDesktopClient bool, hint string) (OAuthFlow, error) {
|
||||||
func NewOAuthFlow(ctx context.Context, config *profilemanager.Config, isUnixDesktopClient bool, forceDeviceCodeFlow bool, hint string) (OAuthFlow, error) {
|
if (runtime.GOOS == "linux" || runtime.GOOS == "freebsd") && !isUnixDesktopClient {
|
||||||
if shouldUseDeviceFlow(forceDeviceCodeFlow, isUnixDesktopClient) {
|
|
||||||
return authenticateWithDeviceCodeFlow(ctx, config, hint)
|
return authenticateWithDeviceCodeFlow(ctx, config, hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/templates"
|
"github.com/netbirdio/netbird/client/internal/templates"
|
||||||
"github.com/netbirdio/netbird/shared/management/client/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ OAuthFlow = &PKCEAuthorizationFlow{}
|
var _ OAuthFlow = &PKCEAuthorizationFlow{}
|
||||||
@@ -48,10 +46,9 @@ type PKCEAuthorizationFlow struct {
|
|||||||
func NewPKCEAuthorizationFlow(config internal.PKCEAuthProviderConfig) (*PKCEAuthorizationFlow, error) {
|
func NewPKCEAuthorizationFlow(config internal.PKCEAuthProviderConfig) (*PKCEAuthorizationFlow, error) {
|
||||||
var availableRedirectURL string
|
var availableRedirectURL string
|
||||||
|
|
||||||
excludedRanges := getSystemExcludedPortRanges()
|
// find the first available redirect URL
|
||||||
|
|
||||||
for _, redirectURL := range config.RedirectURLs {
|
for _, redirectURL := range config.RedirectURLs {
|
||||||
if !isRedirectURLPortUsed(redirectURL, excludedRanges) {
|
if !isRedirectURLPortUsed(redirectURL) {
|
||||||
availableRedirectURL = redirectURL
|
availableRedirectURL = redirectURL
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -105,10 +102,10 @@ func (p *PKCEAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowIn
|
|||||||
oauth2.SetAuthURLParam("audience", p.providerConfig.Audience),
|
oauth2.SetAuthURLParam("audience", p.providerConfig.Audience),
|
||||||
}
|
}
|
||||||
if !p.providerConfig.DisablePromptLogin {
|
if !p.providerConfig.DisablePromptLogin {
|
||||||
switch p.providerConfig.LoginFlag {
|
if p.providerConfig.LoginFlag.IsPromptLogin() {
|
||||||
case common.LoginFlagPromptLogin:
|
|
||||||
params = append(params, oauth2.SetAuthURLParam("prompt", "login"))
|
params = append(params, oauth2.SetAuthURLParam("prompt", "login"))
|
||||||
case common.LoginFlagMaxAge0:
|
}
|
||||||
|
if p.providerConfig.LoginFlag.IsMaxAge0Login() {
|
||||||
params = append(params, oauth2.SetAuthURLParam("max_age", "0"))
|
params = append(params, oauth2.SetAuthURLParam("max_age", "0"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,22 +282,15 @@ func createCodeChallenge(codeVerifier string) string {
|
|||||||
return base64.RawURLEncoding.EncodeToString(sha2[:])
|
return base64.RawURLEncoding.EncodeToString(sha2[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// isRedirectURLPortUsed checks if the port used in the redirect URL is in use or excluded on Windows.
|
// isRedirectURLPortUsed checks if the port used in the redirect URL is in use.
|
||||||
func isRedirectURLPortUsed(redirectURL string, excludedRanges []excludedPortRange) bool {
|
func isRedirectURLPortUsed(redirectURL string) bool {
|
||||||
parsedURL, err := url.Parse(redirectURL)
|
parsedURL, err := url.Parse(redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to parse redirect URL: %v", err)
|
log.Errorf("failed to parse redirect URL: %v", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
port := parsedURL.Port()
|
addr := fmt.Sprintf(":%s", parsedURL.Port())
|
||||||
|
|
||||||
if isPortInExcludedRange(port, excludedRanges) {
|
|
||||||
log.Warnf("port %s is in Windows excluded port range, skipping", port)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := fmt.Sprintf(":%s", port)
|
|
||||||
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -314,33 +304,6 @@ func isRedirectURLPortUsed(redirectURL string, excludedRanges []excludedPortRang
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// excludedPortRange represents a range of excluded ports.
|
|
||||||
type excludedPortRange struct {
|
|
||||||
start int
|
|
||||||
end int
|
|
||||||
}
|
|
||||||
|
|
||||||
// isPortInExcludedRange checks if the given port is in any of the excluded ranges.
|
|
||||||
func isPortInExcludedRange(port string, excludedRanges []excludedPortRange) bool {
|
|
||||||
if len(excludedRanges) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
portNum, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("invalid port number %s: %v", port, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range excludedRanges {
|
|
||||||
if portNum >= r.start && portNum <= r.end {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderPKCEFlowTmpl(w http.ResponseWriter, authError error) {
|
func renderPKCEFlowTmpl(w http.ResponseWriter, authError error) {
|
||||||
tmpl, err := template.New("pkce-auth-flow").Parse(templates.PKCEAuthMsgTmpl)
|
tmpl, err := template.New("pkce-auth-flow").Parse(templates.PKCEAuthMsgTmpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package auth
|
|
||||||
|
|
||||||
// getSystemExcludedPortRanges returns nil on non-Windows platforms.
|
|
||||||
func getSystemExcludedPortRanges() []excludedPortRange {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,8 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
@@ -23,28 +20,22 @@ func TestPromptLogin(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
loginFlag mgm.LoginFlag
|
loginFlag mgm.LoginFlag
|
||||||
disablePromptLogin bool
|
disablePromptLogin bool
|
||||||
expectContains []string
|
expect string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Prompt login",
|
name: "Prompt login",
|
||||||
loginFlag: mgm.LoginFlagPromptLogin,
|
loginFlag: mgm.LoginFlagPrompt,
|
||||||
expectContains: []string{promptLogin},
|
expect: promptLogin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Max age 0",
|
name: "Max age 0 login",
|
||||||
loginFlag: mgm.LoginFlagMaxAge0,
|
loginFlag: mgm.LoginFlagMaxAge0,
|
||||||
expectContains: []string{maxAge0},
|
expect: maxAge0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Disable prompt login",
|
name: "Disable prompt login",
|
||||||
loginFlag: mgm.LoginFlagPromptLogin,
|
loginFlag: mgm.LoginFlagPrompt,
|
||||||
disablePromptLogin: true,
|
disablePromptLogin: true,
|
||||||
expectContains: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "None flag should not add parameters",
|
|
||||||
loginFlag: mgm.LoginFlagNone,
|
|
||||||
expectContains: []string{},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +50,6 @@ func TestPromptLogin(t *testing.T) {
|
|||||||
RedirectURLs: []string{"http://127.0.0.1:33992/"},
|
RedirectURLs: []string{"http://127.0.0.1:33992/"},
|
||||||
UseIDToken: true,
|
UseIDToken: true,
|
||||||
LoginFlag: tc.loginFlag,
|
LoginFlag: tc.loginFlag,
|
||||||
DisablePromptLogin: tc.disablePromptLogin,
|
|
||||||
}
|
}
|
||||||
pkce, err := NewPKCEAuthorizationFlow(config)
|
pkce, err := NewPKCEAuthorizationFlow(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,153 +60,12 @@ func TestPromptLogin(t *testing.T) {
|
|||||||
t.Fatalf("Failed to request auth info: %v", err)
|
t.Fatalf("Failed to request auth info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expected := range tc.expectContains {
|
if !tc.disablePromptLogin {
|
||||||
require.Contains(t, authInfo.VerificationURIComplete, expected)
|
require.Contains(t, authInfo.VerificationURIComplete, tc.expect)
|
||||||
|
} else {
|
||||||
|
require.Contains(t, authInfo.VerificationURIComplete, promptLogin)
|
||||||
|
require.NotContains(t, authInfo.VerificationURIComplete, maxAge0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsPortInExcludedRange(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
port string
|
|
||||||
excludedRanges []excludedPortRange
|
|
||||||
expectedBlocked bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Port in excluded range",
|
|
||||||
port: "8080",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port at start of range",
|
|
||||||
port: "8000",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port at end of range",
|
|
||||||
port: "8100",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port before range",
|
|
||||||
port: "7999",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port after range",
|
|
||||||
port: "8101",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty excluded ranges",
|
|
||||||
port: "8080",
|
|
||||||
excludedRanges: []excludedPortRange{},
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nil excluded ranges",
|
|
||||||
port: "8080",
|
|
||||||
excludedRanges: nil,
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Multiple ranges - port in second range",
|
|
||||||
port: "9050",
|
|
||||||
excludedRanges: []excludedPortRange{
|
|
||||||
{start: 8000, end: 8100},
|
|
||||||
{start: 9000, end: 9100},
|
|
||||||
},
|
|
||||||
expectedBlocked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Multiple ranges - port not in any range",
|
|
||||||
port: "8500",
|
|
||||||
excludedRanges: []excludedPortRange{
|
|
||||||
{start: 8000, end: 8100},
|
|
||||||
{start: 9000, end: 9100},
|
|
||||||
},
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid port string",
|
|
||||||
port: "invalid",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty port string",
|
|
||||||
port: "",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedBlocked: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := isPortInExcludedRange(tt.port, tt.excludedRanges)
|
|
||||||
assert.Equal(t, tt.expectedBlocked, result, "Port exclusion check mismatch")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsRedirectURLPortUsed(t *testing.T) {
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = listener.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
usedPort := listener.Addr().(*net.TCPAddr).Port
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
redirectURL string
|
|
||||||
excludedRanges []excludedPortRange
|
|
||||||
expectedUsed bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Port in excluded range",
|
|
||||||
redirectURL: "http://127.0.0.1:8080/",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedUsed: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port actually in use",
|
|
||||||
redirectURL: fmt.Sprintf("http://127.0.0.1:%d/", usedPort),
|
|
||||||
excludedRanges: nil,
|
|
||||||
expectedUsed: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port not in use and not excluded",
|
|
||||||
redirectURL: "http://127.0.0.1:65432/",
|
|
||||||
excludedRanges: nil,
|
|
||||||
expectedUsed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid URL without port",
|
|
||||||
redirectURL: "not-a-valid-url",
|
|
||||||
excludedRanges: nil,
|
|
||||||
expectedUsed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port excluded even if not in use",
|
|
||||||
redirectURL: "http://127.0.0.1:8050/",
|
|
||||||
excludedRanges: []excludedPortRange{{start: 8000, end: 8100}},
|
|
||||||
expectedUsed: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := isRedirectURLPortUsed(tt.redirectURL, tt.excludedRanges)
|
|
||||||
assert.Equal(t, tt.expectedUsed, result, "Port usage check mismatch")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getSystemExcludedPortRanges retrieves the excluded port ranges from Windows using netsh.
|
|
||||||
func getSystemExcludedPortRanges() []excludedPortRange {
|
|
||||||
ranges, err := getExcludedPortRangesFromNetsh()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("failed to get Windows excluded port ranges: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges
|
|
||||||
}
|
|
||||||
|
|
||||||
// getExcludedPortRangesFromNetsh retrieves excluded port ranges using netsh command.
|
|
||||||
func getExcludedPortRangesFromNetsh() ([]excludedPortRange, error) {
|
|
||||||
cmd := exec.Command("netsh", "interface", "ipv4", "show", "excludedportrange", "protocol=tcp")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("netsh command: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseExcludedPortRanges(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseExcludedPortRanges parses the output of the netsh command to extract port ranges.
|
|
||||||
func parseExcludedPortRanges(output string) ([]excludedPortRange, error) {
|
|
||||||
var ranges []excludedPortRange
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(output))
|
|
||||||
|
|
||||||
foundHeader := false
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
|
|
||||||
if strings.Contains(line, "Start Port") && strings.Contains(line, "End Port") {
|
|
||||||
foundHeader = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !foundHeader {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(line, "----------") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
startPort, err := strconv.Atoi(fields[0])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
endPort, err := strconv.Atoi(fields[1])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges = append(ranges, excludedPortRange{start: startPort, end: endPort})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("scan output: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges, nil
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseExcludedPortRanges(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
netshOutput string
|
|
||||||
expectedRanges []excludedPortRange
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid netsh output with multiple ranges",
|
|
||||||
netshOutput: `
|
|
||||||
Protocol tcp Dynamic Port Range
|
|
||||||
---------------------------------
|
|
||||||
Start Port : 49152
|
|
||||||
Number of Ports : 16384
|
|
||||||
|
|
||||||
Protocol tcp Excluded Port Ranges
|
|
||||||
---------------------------------
|
|
||||||
Start Port End Port
|
|
||||||
---------- --------
|
|
||||||
5357 5357 *
|
|
||||||
50000 50059 *
|
|
||||||
`,
|
|
||||||
expectedRanges: []excludedPortRange{
|
|
||||||
{start: 5357, end: 5357},
|
|
||||||
{start: 50000, end: 50059},
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty output",
|
|
||||||
netshOutput: `
|
|
||||||
Protocol tcp Dynamic Port Range
|
|
||||||
---------------------------------
|
|
||||||
Start Port : 49152
|
|
||||||
Number of Ports : 16384
|
|
||||||
`,
|
|
||||||
expectedRanges: nil,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Single range",
|
|
||||||
netshOutput: `
|
|
||||||
Protocol tcp Excluded Port Ranges
|
|
||||||
---------------------------------
|
|
||||||
Start Port End Port
|
|
||||||
---------- --------
|
|
||||||
8080 8090
|
|
||||||
`,
|
|
||||||
expectedRanges: []excludedPortRange{
|
|
||||||
{start: 8080, end: 8090},
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ranges, err := parseExcludedPortRanges(tt.netshOutput)
|
|
||||||
|
|
||||||
if tt.expectError {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.expectedRanges, ranges)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewPKCEAuthorizationFlow_WithActualExcludedPorts(t *testing.T) {
|
|
||||||
ranges := getSystemExcludedPortRanges()
|
|
||||||
t.Logf("Found %d excluded port ranges on this system", len(ranges))
|
|
||||||
|
|
||||||
listener1, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
_ = listener1.Close()
|
|
||||||
}()
|
|
||||||
usedPort1 := listener1.Addr().(*net.TCPAddr).Port
|
|
||||||
|
|
||||||
availablePort := 65432
|
|
||||||
|
|
||||||
config := internal.PKCEAuthProviderConfig{
|
|
||||||
ClientID: "test-client-id",
|
|
||||||
Audience: "test-audience",
|
|
||||||
TokenEndpoint: "https://test-token-endpoint.com/token",
|
|
||||||
Scope: "openid email profile",
|
|
||||||
AuthorizationEndpoint: "https://test-auth-endpoint.com/authorize",
|
|
||||||
RedirectURLs: []string{
|
|
||||||
fmt.Sprintf("http://127.0.0.1:%d/", usedPort1),
|
|
||||||
fmt.Sprintf("http://127.0.0.1:%d/", availablePort),
|
|
||||||
},
|
|
||||||
UseIDToken: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
flow, err := NewPKCEAuthorizationFlow(config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, flow)
|
|
||||||
assert.Contains(t, flow.oAuthConfig.RedirectURL, fmt.Sprintf(":%d", availablePort),
|
|
||||||
"Should skip port in use and select available port")
|
|
||||||
}
|
|
||||||
@@ -52,7 +52,6 @@ func NewConnectClient(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *profilemanager.Config,
|
config *profilemanager.Config,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
|
|
||||||
) *ConnectClient {
|
) *ConnectClient {
|
||||||
return &ConnectClient{
|
return &ConnectClient{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -63,8 +62,8 @@ func NewConnectClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run with main logic.
|
// Run with main logic.
|
||||||
func (c *ConnectClient) Run(runningChan chan struct{}) error {
|
func (c *ConnectClient) Run(runningChan chan struct{}, logPath string) error {
|
||||||
return c.run(MobileDependency{}, runningChan)
|
return c.run(MobileDependency{}, runningChan, logPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunOnAndroid with main logic on mobile system
|
// RunOnAndroid with main logic on mobile system
|
||||||
@@ -74,7 +73,6 @@ func (c *ConnectClient) RunOnAndroid(
|
|||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
dnsAddresses []netip.AddrPort,
|
dnsAddresses []netip.AddrPort,
|
||||||
dnsReadyListener dns.ReadyListener,
|
dnsReadyListener dns.ReadyListener,
|
||||||
stateFilePath string,
|
|
||||||
) error {
|
) error {
|
||||||
// in case of non Android os these variables will be nil
|
// in case of non Android os these variables will be nil
|
||||||
mobileDependency := MobileDependency{
|
mobileDependency := MobileDependency{
|
||||||
@@ -83,9 +81,8 @@ func (c *ConnectClient) RunOnAndroid(
|
|||||||
NetworkChangeListener: networkChangeListener,
|
NetworkChangeListener: networkChangeListener,
|
||||||
HostDNSAddresses: dnsAddresses,
|
HostDNSAddresses: dnsAddresses,
|
||||||
DnsReadyListener: dnsReadyListener,
|
DnsReadyListener: dnsReadyListener,
|
||||||
StateFilePath: stateFilePath,
|
|
||||||
}
|
}
|
||||||
return c.run(mobileDependency, nil)
|
return c.run(mobileDependency, nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnectClient) RunOniOS(
|
func (c *ConnectClient) RunOniOS(
|
||||||
@@ -103,10 +100,10 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
DnsManager: dnsManager,
|
DnsManager: dnsManager,
|
||||||
StateFilePath: stateFilePath,
|
StateFilePath: stateFilePath,
|
||||||
}
|
}
|
||||||
return c.run(mobileDependency, nil)
|
return c.run(mobileDependency, nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan struct{}) error {
|
func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan struct{}, logPath string) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
rec := c.statusRecorder
|
rec := c.statusRecorder
|
||||||
@@ -249,7 +246,7 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
|||||||
relayURLs, token := parseRelayInfo(loginResp)
|
relayURLs, token := parseRelayInfo(loginResp)
|
||||||
peerConfig := loginResp.GetPeerConfig()
|
peerConfig := loginResp.GetPeerConfig()
|
||||||
|
|
||||||
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig)
|
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig, logPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
@@ -273,12 +270,11 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
|||||||
checks := loginResp.GetChecks()
|
checks := loginResp.GetChecks()
|
||||||
|
|
||||||
c.engineMutex.Lock()
|
c.engineMutex.Lock()
|
||||||
engine := NewEngine(engineCtx, cancel, signalClient, mgmClient, relayManager, engineConfig, mobileDependency, c.statusRecorder, checks)
|
c.engine = NewEngine(engineCtx, cancel, signalClient, mgmClient, relayManager, engineConfig, mobileDependency, c.statusRecorder, checks, c.config)
|
||||||
engine.SetSyncResponsePersistence(c.persistSyncResponse)
|
c.engine.SetSyncResponsePersistence(c.persistSyncResponse)
|
||||||
c.engine = engine
|
|
||||||
c.engineMutex.Unlock()
|
c.engineMutex.Unlock()
|
||||||
|
|
||||||
if err := engine.Start(loginResp.GetNetbirdConfig(), c.config.ManagementURL); err != nil {
|
if err := c.engine.Start(loginResp.GetNetbirdConfig(), c.config.ManagementURL); err != nil {
|
||||||
log.Errorf("error while starting Netbird Connection Engine: %s", err)
|
log.Errorf("error while starting Netbird Connection Engine: %s", err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
@@ -294,14 +290,12 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
|||||||
<-engineCtx.Done()
|
<-engineCtx.Done()
|
||||||
|
|
||||||
c.engineMutex.Lock()
|
c.engineMutex.Lock()
|
||||||
|
engine := c.engine
|
||||||
c.engine = nil
|
c.engine = nil
|
||||||
c.engineMutex.Unlock()
|
c.engineMutex.Unlock()
|
||||||
|
|
||||||
// todo: consider to remove this condition. Is not thread safe.
|
if engine != nil && engine.wgInterface != nil {
|
||||||
// We should always call Stop(), but we need to verify that it is idempotent
|
|
||||||
if engine.wgInterface != nil {
|
|
||||||
log.Infof("ensuring %s is removed, Netbird engine context cancelled", engine.wgInterface.Name())
|
log.Infof("ensuring %s is removed, Netbird engine context cancelled", engine.wgInterface.Name())
|
||||||
|
|
||||||
if err := engine.Stop(); err != nil {
|
if err := engine.Stop(); err != nil {
|
||||||
log.Errorf("Failed to stop engine: %v", err)
|
log.Errorf("Failed to stop engine: %v", err)
|
||||||
}
|
}
|
||||||
@@ -415,7 +409,7 @@ func (c *ConnectClient) SetSyncResponsePersistence(enabled bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||||
func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConfig *mgmProto.PeerConfig, logPath string) (*EngineConfig, error) {
|
||||||
nm := false
|
nm := false
|
||||||
if config.NetworkMonitor != nil {
|
if config.NetworkMonitor != nil {
|
||||||
nm = *config.NetworkMonitor
|
nm = *config.NetworkMonitor
|
||||||
@@ -450,7 +444,10 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf
|
|||||||
|
|
||||||
LazyConnectionEnabled: config.LazyConnectionEnabled,
|
LazyConnectionEnabled: config.LazyConnectionEnabled,
|
||||||
|
|
||||||
MTU: selectMTU(config.MTU, peerConfig.Mtu),
|
MTU: selectMTU(config.MTU, peerConfig.Mtu),
|
||||||
|
LogPath: logPath,
|
||||||
|
|
||||||
|
ProfileConfig: config,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.PreSharedKey != "" {
|
if config.PreSharedKey != "" {
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/anonymize"
|
"github.com/netbirdio/netbird/client/anonymize"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const readmeContent = `Netbird debug bundle
|
const readmeContent = `Netbird debug bundle
|
||||||
@@ -218,10 +220,9 @@ type BundleGenerator struct {
|
|||||||
internalConfig *profilemanager.Config
|
internalConfig *profilemanager.Config
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
syncResponse *mgmProto.SyncResponse
|
syncResponse *mgmProto.SyncResponse
|
||||||
logFile string
|
logPath string
|
||||||
|
|
||||||
anonymize bool
|
anonymize bool
|
||||||
clientStatus string
|
|
||||||
includeSystemInfo bool
|
includeSystemInfo bool
|
||||||
logFileCount uint32
|
logFileCount uint32
|
||||||
|
|
||||||
@@ -230,7 +231,6 @@ type BundleGenerator struct {
|
|||||||
|
|
||||||
type BundleConfig struct {
|
type BundleConfig struct {
|
||||||
Anonymize bool
|
Anonymize bool
|
||||||
ClientStatus string
|
|
||||||
IncludeSystemInfo bool
|
IncludeSystemInfo bool
|
||||||
LogFileCount uint32
|
LogFileCount uint32
|
||||||
}
|
}
|
||||||
@@ -239,7 +239,7 @@ type GeneratorDependencies struct {
|
|||||||
InternalConfig *profilemanager.Config
|
InternalConfig *profilemanager.Config
|
||||||
StatusRecorder *peer.Status
|
StatusRecorder *peer.Status
|
||||||
SyncResponse *mgmProto.SyncResponse
|
SyncResponse *mgmProto.SyncResponse
|
||||||
LogFile string
|
LogPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGenerator {
|
func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGenerator {
|
||||||
@@ -255,10 +255,9 @@ func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGen
|
|||||||
internalConfig: deps.InternalConfig,
|
internalConfig: deps.InternalConfig,
|
||||||
statusRecorder: deps.StatusRecorder,
|
statusRecorder: deps.StatusRecorder,
|
||||||
syncResponse: deps.SyncResponse,
|
syncResponse: deps.SyncResponse,
|
||||||
logFile: deps.LogFile,
|
logPath: deps.LogPath,
|
||||||
|
|
||||||
anonymize: cfg.Anonymize,
|
anonymize: cfg.Anonymize,
|
||||||
clientStatus: cfg.ClientStatus,
|
|
||||||
includeSystemInfo: cfg.IncludeSystemInfo,
|
includeSystemInfo: cfg.IncludeSystemInfo,
|
||||||
logFileCount: logFileCount,
|
logFileCount: logFileCount,
|
||||||
}
|
}
|
||||||
@@ -304,13 +303,6 @@ func (g *BundleGenerator) createArchive() error {
|
|||||||
return fmt.Errorf("add status: %w", err)
|
return fmt.Errorf("add status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.statusRecorder != nil {
|
|
||||||
status := g.statusRecorder.GetFullStatus()
|
|
||||||
seedFromStatus(g.anonymizer, &status)
|
|
||||||
} else {
|
|
||||||
log.Debugf("no status recorder available for seeding")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.addConfig(); err != nil {
|
if err := g.addConfig(); err != nil {
|
||||||
log.Errorf("failed to add config to debug bundle: %v", err)
|
log.Errorf("failed to add config to debug bundle: %v", err)
|
||||||
}
|
}
|
||||||
@@ -343,7 +335,7 @@ func (g *BundleGenerator) createArchive() error {
|
|||||||
log.Errorf("failed to add wg show output: %v", err)
|
log.Errorf("failed to add wg show output: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.logFile != "" && !slices.Contains(util.SpecialLogs, g.logFile) {
|
if g.logPath != "" && !slices.Contains(util.SpecialLogs, g.logPath) {
|
||||||
if err := g.addLogfile(); err != nil {
|
if err := g.addLogfile(); err != nil {
|
||||||
log.Errorf("failed to add log file to debug bundle: %v", err)
|
log.Errorf("failed to add log file to debug bundle: %v", err)
|
||||||
if err := g.trySystemdLogFallback(); err != nil {
|
if err := g.trySystemdLogFallback(); err != nil {
|
||||||
@@ -388,11 +380,26 @@ func (g *BundleGenerator) addReadme() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *BundleGenerator) addStatus() error {
|
func (g *BundleGenerator) addStatus() error {
|
||||||
if status := g.clientStatus; status != "" {
|
if g.statusRecorder != nil {
|
||||||
statusReader := strings.NewReader(status)
|
pm := profilemanager.NewProfileManager()
|
||||||
|
var profName string
|
||||||
|
if activeProf, err := pm.GetActiveProfile(); err == nil {
|
||||||
|
profName = activeProf.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
fullStatus := g.statusRecorder.GetFullStatus()
|
||||||
|
protoFullStatus := nbstatus.ToProtoFullStatus(fullStatus)
|
||||||
|
protoFullStatus.Events = g.statusRecorder.GetEventHistory()
|
||||||
|
overview := nbstatus.ConvertToStatusOutputOverview(protoFullStatus, g.anonymize, version.NetbirdVersion(), "", nil, nil, nil, "", profName)
|
||||||
|
statusOutput := nbstatus.ParseToFullDetailSummary(overview)
|
||||||
|
|
||||||
|
statusReader := strings.NewReader(statusOutput)
|
||||||
if err := g.addFileToZip(statusReader, "status.txt"); err != nil {
|
if err := g.addFileToZip(statusReader, "status.txt"); err != nil {
|
||||||
return fmt.Errorf("add status file to zip: %w", err)
|
return fmt.Errorf("add status file to zip: %w", err)
|
||||||
}
|
}
|
||||||
|
seedFromStatus(g.anonymizer, &fullStatus)
|
||||||
|
} else {
|
||||||
|
log.Debugf("no status recorder available for seeding")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -662,14 +669,14 @@ func (g *BundleGenerator) addCorruptedStateFiles() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *BundleGenerator) addLogfile() error {
|
func (g *BundleGenerator) addLogfile() error {
|
||||||
if g.logFile == "" {
|
if g.logPath == "" {
|
||||||
log.Debugf("skipping empty log file in debug bundle")
|
log.Debugf("skipping empty log file in debug bundle")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logDir := filepath.Dir(g.logFile)
|
logDir := filepath.Dir(g.logPath)
|
||||||
|
|
||||||
if err := g.addSingleLogfile(g.logFile, clientLogFile); err != nil {
|
if err := g.addSingleLogfile(g.logPath, clientLogFile); err != nil {
|
||||||
return fmt.Errorf("add client log file to zip: %w", err)
|
return fmt.Errorf("add client log file to zip: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
101
client/internal/debug/upload.go
Normal file
101
client/internal/debug/upload.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/upload-server/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxBundleUploadSize = 50 * 1024 * 1024
|
||||||
|
|
||||||
|
func UploadDebugBundle(ctx context.Context, url, managementURL, filePath string) (key string, err error) {
|
||||||
|
response, err := getUploadURL(ctx, url, managementURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = upload(ctx, filePath, response)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return response.Key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func upload(ctx context.Context, filePath string, response *types.GetURLResponse) error {
|
||||||
|
fileData, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fileData.Close()
|
||||||
|
|
||||||
|
stat, err := fileData.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.Size() > maxBundleUploadSize {
|
||||||
|
return fmt.Errorf("file size exceeds maximum limit of %d bytes", maxBundleUploadSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "PUT", response.URL, fileData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create PUT request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ContentLength = stat.Size()
|
||||||
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
|
putResp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("upload failed: %v", err)
|
||||||
|
}
|
||||||
|
defer putResp.Body.Close()
|
||||||
|
|
||||||
|
if putResp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(putResp.Body)
|
||||||
|
return fmt.Errorf("upload status %d: %s", putResp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUploadURL(ctx context.Context, url string, managementURL string) (*types.GetURLResponse, error) {
|
||||||
|
id := getURLHash(managementURL)
|
||||||
|
getReq, err := http.NewRequestWithContext(ctx, "GET", url+"?id="+id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create GET request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
getReq.Header.Set(types.ClientHeader, types.ClientHeaderValue)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(getReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get presigned URL: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("get presigned URL status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
urlBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read response body: %w", err)
|
||||||
|
}
|
||||||
|
var response types.GetURLResponse
|
||||||
|
if err := json.Unmarshal(urlBytes, &response); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getURLHash(url string) string {
|
||||||
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package server
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -38,7 +38,7 @@ func TestUpload(t *testing.T) {
|
|||||||
fileContent := []byte("test file content")
|
fileContent := []byte("test file content")
|
||||||
err := os.WriteFile(file, fileContent, 0640)
|
err := os.WriteFile(file, fileContent, 0640)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
key, err := uploadDebugBundle(context.Background(), testURL+types.GetURLPath, testURL, file)
|
key, err := UploadDebugBundle(context.Background(), testURL+types.GetURLPath, testURL, file)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
id := getURLHash(testURL)
|
id := getURLHash(testURL)
|
||||||
require.Contains(t, key, id+"/")
|
require.Contains(t, key, id+"/")
|
||||||
@@ -76,9 +76,6 @@ func collectPTRRecords(config *nbdns.Config, prefix netip.Prefix) []nbdns.Simple
|
|||||||
var records []nbdns.SimpleRecord
|
var records []nbdns.SimpleRecord
|
||||||
|
|
||||||
for _, zone := range config.CustomZones {
|
for _, zone := range config.CustomZones {
|
||||||
if zone.SkipPTRProcess {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, record := range zone.Records {
|
for _, record := range zone.Records {
|
||||||
if record.Type != int(dns.TypeA) {
|
if record.Type != int(dns.TypeA) {
|
||||||
continue
|
continue
|
||||||
@@ -109,9 +106,8 @@ func addReverseZone(config *nbdns.Config, network netip.Prefix) {
|
|||||||
records := collectPTRRecords(config, network)
|
records := collectPTRRecords(config, network)
|
||||||
|
|
||||||
reverseZone := nbdns.CustomZone{
|
reverseZone := nbdns.CustomZone{
|
||||||
Domain: zoneName,
|
Domain: zoneName,
|
||||||
Records: records,
|
Records: records,
|
||||||
SearchDomainDisabled: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.CustomZones = append(config.CustomZones, reverseZone)
|
config.CustomZones = append(config.CustomZones, reverseZone)
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import (
|
|||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipv4ReverseZone = ".in-addr.arpa."
|
||||||
|
ipv6ReverseZone = ".ip6.arpa."
|
||||||
|
)
|
||||||
|
|
||||||
type hostManager interface {
|
type hostManager interface {
|
||||||
applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error
|
applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error
|
||||||
restoreHostDNS() error
|
restoreHostDNS() error
|
||||||
@@ -105,9 +110,10 @@ func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip netip.Addr, port int) H
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, customZone := range dnsConfig.CustomZones {
|
for _, customZone := range dnsConfig.CustomZones {
|
||||||
|
matchOnly := strings.HasSuffix(customZone.Domain, ipv4ReverseZone) || strings.HasSuffix(customZone.Domain, ipv6ReverseZone)
|
||||||
config.Domains = append(config.Domains, DomainConfig{
|
config.Domains = append(config.Domains, DomainConfig{
|
||||||
Domain: strings.ToLower(dns.Fqdn(customZone.Domain)),
|
Domain: strings.ToLower(dns.Fqdn(customZone.Domain)),
|
||||||
MatchOnly: customZone.SearchDomainDisabled,
|
MatchOnly: matchOnly,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func (u *upstreamResolverBase) handleUpstreamError(err error, upstream netip.Add
|
|||||||
timeoutMsg += " " + peerInfo
|
timeoutMsg += " " + peerInfo
|
||||||
}
|
}
|
||||||
timeoutMsg += fmt.Sprintf(" - error: %v", err)
|
timeoutMsg += fmt.Sprintf(" - error: %v", err)
|
||||||
logger.Warn(timeoutMsg)
|
logger.Warnf(timeoutMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *upstreamResolverBase) writeSuccessResponse(w dns.ResponseWriter, rm *dns.Msg, upstream netip.AddrPort, domain string, t time.Duration, logger *log.Entry) bool {
|
func (u *upstreamResolverBase) writeSuccessResponse(w dns.ResponseWriter, rm *dns.Msg, upstream netip.AddrPort, domain string, t time.Duration, logger *log.Entry) bool {
|
||||||
|
|||||||
@@ -234,11 +234,6 @@ func (f *DNSForwarder) handleDNSQuery(w dns.ResponseWriter, query *dns.Msg) *dns
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmap IPv4-mapped IPv6 addresses that some resolvers may return
|
|
||||||
for i, ip := range ips {
|
|
||||||
ips[i] = ip.Unmap()
|
|
||||||
}
|
|
||||||
|
|
||||||
f.updateInternalState(ips, mostSpecificResId, matchingEntries)
|
f.updateInternalState(ips, mostSpecificResId, matchingEntries)
|
||||||
f.addIPsToResponse(resp, domain, ips)
|
f.addIPsToResponse(resp, domain, ips)
|
||||||
f.cache.set(domain, question.Qtype, ips)
|
f.cache.set(domain, question.Qtype, ips)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
"github.com/netbirdio/netbird/client/iface/udpmux"
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/internal/acl"
|
"github.com/netbirdio/netbird/client/internal/acl"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/debug"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
||||||
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
||||||
@@ -48,6 +49,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
||||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||||
|
"github.com/netbirdio/netbird/client/jobexec"
|
||||||
cProto "github.com/netbirdio/netbird/client/proto"
|
cProto "github.com/netbirdio/netbird/client/proto"
|
||||||
sshconfig "github.com/netbirdio/netbird/client/ssh/config"
|
sshconfig "github.com/netbirdio/netbird/client/ssh/config"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
@@ -132,6 +134,11 @@ type EngineConfig struct {
|
|||||||
LazyConnectionEnabled bool
|
LazyConnectionEnabled bool
|
||||||
|
|
||||||
MTU uint16
|
MTU uint16
|
||||||
|
|
||||||
|
// for debug bundle generation
|
||||||
|
ProfileConfig *profilemanager.Config
|
||||||
|
|
||||||
|
LogPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||||
@@ -195,7 +202,8 @@ type Engine struct {
|
|||||||
stateManager *statemanager.Manager
|
stateManager *statemanager.Manager
|
||||||
srWatcher *guard.SRWatcher
|
srWatcher *guard.SRWatcher
|
||||||
|
|
||||||
// Sync response persistence
|
// Sync response persistence (protected by syncRespMux)
|
||||||
|
syncRespMux sync.RWMutex
|
||||||
persistSyncResponse bool
|
persistSyncResponse bool
|
||||||
latestSyncResponse *mgmProto.SyncResponse
|
latestSyncResponse *mgmProto.SyncResponse
|
||||||
connSemaphore *semaphoregroup.SemaphoreGroup
|
connSemaphore *semaphoregroup.SemaphoreGroup
|
||||||
@@ -208,6 +216,9 @@ type Engine struct {
|
|||||||
shutdownWg sync.WaitGroup
|
shutdownWg sync.WaitGroup
|
||||||
|
|
||||||
probeStunTurn *relay.StunTurnProbe
|
probeStunTurn *relay.StunTurnProbe
|
||||||
|
|
||||||
|
jobExecutor *jobexec.Executor
|
||||||
|
jobExecutorWG sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -221,17 +232,7 @@ type localIpUpdater interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEngine creates a new Connection Engine with probes attached
|
// NewEngine creates a new Connection Engine with probes attached
|
||||||
func NewEngine(
|
func NewEngine(clientCtx context.Context, clientCancel context.CancelFunc, signalClient signal.Client, mgmClient mgm.Client, relayManager *relayClient.Manager, config *EngineConfig, mobileDep MobileDependency, statusRecorder *peer.Status, checks []*mgmProto.Checks, c *profilemanager.Config) *Engine {
|
||||||
clientCtx context.Context,
|
|
||||||
clientCancel context.CancelFunc,
|
|
||||||
signalClient signal.Client,
|
|
||||||
mgmClient mgm.Client,
|
|
||||||
relayManager *relayClient.Manager,
|
|
||||||
config *EngineConfig,
|
|
||||||
mobileDep MobileDependency,
|
|
||||||
statusRecorder *peer.Status,
|
|
||||||
checks []*mgmProto.Checks,
|
|
||||||
) *Engine {
|
|
||||||
engine := &Engine{
|
engine := &Engine{
|
||||||
clientCtx: clientCtx,
|
clientCtx: clientCtx,
|
||||||
clientCancel: clientCancel,
|
clientCancel: clientCancel,
|
||||||
@@ -250,12 +251,13 @@ func NewEngine(
|
|||||||
checks: checks,
|
checks: checks,
|
||||||
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
|
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
|
||||||
probeStunTurn: relay.NewStunTurnProbe(relay.DefaultCacheTTL),
|
probeStunTurn: relay.NewStunTurnProbe(relay.DefaultCacheTTL),
|
||||||
|
jobExecutor: jobexec.NewExecutor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
sm := profilemanager.NewServiceManager("")
|
sm := profilemanager.NewServiceManager("")
|
||||||
|
|
||||||
path := sm.GetStatePath()
|
path := sm.GetStatePath()
|
||||||
if runtime.GOOS == "ios" || runtime.GOOS == "android" {
|
if runtime.GOOS == "ios" {
|
||||||
if !fileExists(mobileDep.StateFilePath) {
|
if !fileExists(mobileDep.StateFilePath) {
|
||||||
err := createFile(mobileDep.StateFilePath)
|
err := createFile(mobileDep.StateFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,6 +282,7 @@ func (e *Engine) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
if e.connMgr != nil {
|
if e.connMgr != nil {
|
||||||
e.connMgr.Close()
|
e.connMgr.Close()
|
||||||
@@ -297,6 +300,9 @@ func (e *Engine) Stop() error {
|
|||||||
|
|
||||||
e.cleanupSSHConfig()
|
e.cleanupSSHConfig()
|
||||||
|
|
||||||
|
// stop/restore DNS first so dbus and friends don't complain because of a missing interface
|
||||||
|
e.stopDNSServer()
|
||||||
|
|
||||||
if e.ingressGatewayMgr != nil {
|
if e.ingressGatewayMgr != nil {
|
||||||
if err := e.ingressGatewayMgr.Close(); err != nil {
|
if err := e.ingressGatewayMgr.Close(); err != nil {
|
||||||
log.Warnf("failed to cleanup forward rules: %v", err)
|
log.Warnf("failed to cleanup forward rules: %v", err)
|
||||||
@@ -304,33 +310,30 @@ func (e *Engine) Stop() error {
|
|||||||
e.ingressGatewayMgr = nil
|
e.ingressGatewayMgr = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.srWatcher != nil {
|
e.stopDNSForwarder()
|
||||||
e.srWatcher.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("cleaning up status recorder states")
|
|
||||||
e.statusRecorder.ReplaceOfflinePeers([]peer.State{})
|
|
||||||
e.statusRecorder.UpdateDNSStates([]peer.NSGroupState{})
|
|
||||||
e.statusRecorder.UpdateRelayStates([]relay.ProbeResult{})
|
|
||||||
|
|
||||||
if err := e.removeAllPeers(); err != nil {
|
|
||||||
log.Errorf("failed to remove all peers: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.routeManager != nil {
|
if e.routeManager != nil {
|
||||||
e.routeManager.Stop(e.stateManager)
|
e.routeManager.Stop(e.stateManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.stopDNSForwarder()
|
if e.srWatcher != nil {
|
||||||
|
e.srWatcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// stop/restore DNS after peers are closed but before interface goes down
|
e.statusRecorder.ReplaceOfflinePeers([]peer.State{})
|
||||||
// so dbus and friends don't complain because of a missing interface
|
e.statusRecorder.UpdateDNSStates([]peer.NSGroupState{})
|
||||||
e.stopDNSServer()
|
e.statusRecorder.UpdateRelayStates([]relay.ProbeResult{})
|
||||||
|
|
||||||
|
if err := e.removeAllPeers(); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove all peers: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
if e.cancel != nil {
|
if e.cancel != nil {
|
||||||
e.cancel()
|
e.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.jobExecutorWG.Wait() // block until job goroutines finish
|
||||||
|
|
||||||
e.close()
|
e.close()
|
||||||
|
|
||||||
// stop flow manager after wg interface is gone
|
// stop flow manager after wg interface is gone
|
||||||
@@ -338,18 +341,16 @@ func (e *Engine) Stop() error {
|
|||||||
e.flowManager.Close()
|
e.flowManager.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
stateCtx, stateCancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer stateCancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := e.stateManager.Stop(stateCtx); err != nil {
|
if err := e.stateManager.Stop(ctx); err != nil {
|
||||||
log.Errorf("failed to stop state manager: %v", err)
|
return fmt.Errorf("failed to stop state manager: %w", err)
|
||||||
}
|
}
|
||||||
if err := e.stateManager.PersistState(context.Background()); err != nil {
|
if err := e.stateManager.PersistState(context.Background()); err != nil {
|
||||||
log.Errorf("failed to persist state: %v", err)
|
log.Errorf("failed to persist state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.syncMsgMux.Unlock()
|
|
||||||
|
|
||||||
timeout := e.calculateShutdownTimeout()
|
timeout := e.calculateShutdownTimeout()
|
||||||
log.Debugf("waiting for goroutines to finish with timeout: %v", timeout)
|
log.Debugf("waiting for goroutines to finish with timeout: %v", timeout)
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), timeout)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
@@ -435,7 +436,8 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create rosenpass manager: %w", err)
|
return fmt.Errorf("create rosenpass manager: %w", err)
|
||||||
}
|
}
|
||||||
if err := e.rpManager.Run(); err != nil {
|
err := e.rpManager.Run()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("run rosenpass manager: %w", err)
|
return fmt.Errorf("run rosenpass manager: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,7 +489,6 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := e.createFirewall(); err != nil {
|
if err := e.createFirewall(); err != nil {
|
||||||
e.close()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,6 +520,7 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
|||||||
|
|
||||||
e.receiveSignalEvents()
|
e.receiveSignalEvents()
|
||||||
e.receiveManagementEvents()
|
e.receiveManagementEvents()
|
||||||
|
e.receiveJobEvents()
|
||||||
|
|
||||||
// starting network monitor at the very last to avoid disruptions
|
// starting network monitor at the very last to avoid disruptions
|
||||||
e.startNetworkMonitor()
|
e.startNetworkMonitor()
|
||||||
@@ -753,11 +755,6 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
|||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
// Check context INSIDE lock to ensure atomicity with shutdown
|
|
||||||
if e.ctx.Err() != nil {
|
|
||||||
return e.ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
if update.GetNetbirdConfig() != nil {
|
if update.GetNetbirdConfig() != nil {
|
||||||
wCfg := update.GetNetbirdConfig()
|
wCfg := update.GetNetbirdConfig()
|
||||||
err := e.updateTURNs(wCfg.GetTurns())
|
err := e.updateTURNs(wCfg.GetTurns())
|
||||||
@@ -797,13 +794,22 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nm := update.GetNetworkMap()
|
nm := update.GetNetworkMap()
|
||||||
if nm == nil || update.SkipNetworkMapUpdate {
|
if nm == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist sync response under the dedicated lock (syncRespMux), not under syncMsgMux.
|
||||||
|
// Read the storage-enabled flag under the syncRespMux too.
|
||||||
|
e.syncRespMux.RLock()
|
||||||
|
enabled := e.persistSyncResponse
|
||||||
|
e.syncRespMux.RUnlock()
|
||||||
|
|
||||||
// Store sync response if persistence is enabled
|
// Store sync response if persistence is enabled
|
||||||
if e.persistSyncResponse {
|
if enabled {
|
||||||
|
e.syncRespMux.Lock()
|
||||||
e.latestSyncResponse = update
|
e.latestSyncResponse = update
|
||||||
|
e.syncRespMux.Unlock()
|
||||||
|
|
||||||
log.Debugf("sync response persisted with serial %d", nm.GetSerial())
|
log.Debugf("sync response persisted with serial %d", nm.GetSerial())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,6 +939,77 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (e *Engine) receiveJobEvents() {
|
||||||
|
e.jobExecutorWG.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer e.jobExecutorWG.Done()
|
||||||
|
err := e.mgmClient.Job(e.ctx, func(msg *mgmProto.JobRequest) *mgmProto.JobResponse {
|
||||||
|
resp := mgmProto.JobResponse{
|
||||||
|
ID: msg.ID,
|
||||||
|
Status: mgmProto.JobStatus_failed,
|
||||||
|
}
|
||||||
|
switch params := msg.WorkloadParameters.(type) {
|
||||||
|
case *mgmProto.JobRequest_Bundle:
|
||||||
|
bundleResult, err := e.handleBundle(params.Bundle)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("handling bundle: %v", err)
|
||||||
|
resp.Reason = []byte(err.Error())
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
resp.Status = mgmProto.JobStatus_succeeded
|
||||||
|
resp.WorkloadResults = bundleResult
|
||||||
|
return &resp
|
||||||
|
default:
|
||||||
|
resp.Reason = []byte(jobexec.ErrJobNotImplemented.Error())
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// happens if management is unavailable for a long time.
|
||||||
|
// We want to cancel the operation of the whole client
|
||||||
|
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||||
|
e.clientCancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("stopped receiving jobs from Management Service")
|
||||||
|
}()
|
||||||
|
log.Info("connecting to Management Service jobs stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) handleBundle(params *mgmProto.BundleParameters) (*mgmProto.JobResponse_Bundle, error) {
|
||||||
|
log.Infof("handle remote debug bundle request: %s", params.String())
|
||||||
|
syncResponse, err := e.GetLatestSyncResponse()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("get latest sync response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleDeps := debug.GeneratorDependencies{
|
||||||
|
InternalConfig: e.config.ProfileConfig,
|
||||||
|
StatusRecorder: e.statusRecorder,
|
||||||
|
SyncResponse: syncResponse,
|
||||||
|
LogPath: e.config.LogPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleJobParams := debug.BundleConfig{
|
||||||
|
Anonymize: params.Anonymize,
|
||||||
|
IncludeSystemInfo: true,
|
||||||
|
LogFileCount: uint32(params.LogFileCount),
|
||||||
|
}
|
||||||
|
|
||||||
|
waitFor := time.Duration(params.BundleForTime) * time.Minute
|
||||||
|
|
||||||
|
uploadKey, err := e.jobExecutor.BundleJob(e.ctx, bundleDeps, bundleJobParams, waitFor, e.config.ProfileConfig.ManagementURL.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &mgmProto.JobResponse_Bundle{
|
||||||
|
Bundle: &mgmProto.BundleResult{
|
||||||
|
UploadKey: uploadKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
|
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
|
||||||
// E.g. when a new peer has been registered and we are allowed to connect to it.
|
// E.g. when a new peer has been registered and we are allowed to connect to it.
|
||||||
@@ -963,7 +1040,7 @@ func (e *Engine) receiveManagementEvents() {
|
|||||||
e.config.DisableSSHAuth,
|
e.config.DisableSSHAuth,
|
||||||
)
|
)
|
||||||
|
|
||||||
err = e.mgmClient.Sync(e.ctx, info, e.networkSerial, e.handleSync)
|
err = e.mgmClient.Sync(e.ctx, info, e.handleSync)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// happens if management is unavailable for a long time.
|
// happens if management is unavailable for a long time.
|
||||||
// We want to cancel the operation of the whole client
|
// We want to cancel the operation of the whole client
|
||||||
@@ -1200,8 +1277,7 @@ func toRouteDomains(myPubKey string, routes []*route.Route) []*dnsfwd.ForwarderE
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig, network netip.Prefix) nbdns.Config {
|
func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig, network netip.Prefix) nbdns.Config {
|
||||||
//nolint
|
forwarderPort := uint16(protoDNSConfig.GetForwarderPort()) //nolint
|
||||||
forwarderPort := uint16(protoDNSConfig.GetForwarderPort())
|
|
||||||
if forwarderPort == 0 {
|
if forwarderPort == 0 {
|
||||||
forwarderPort = nbdns.ForwarderClientPort
|
forwarderPort = nbdns.ForwarderClientPort
|
||||||
}
|
}
|
||||||
@@ -1215,9 +1291,7 @@ func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig, network netip.Prefix) nbdns
|
|||||||
|
|
||||||
for _, zone := range protoDNSConfig.GetCustomZones() {
|
for _, zone := range protoDNSConfig.GetCustomZones() {
|
||||||
dnsZone := nbdns.CustomZone{
|
dnsZone := nbdns.CustomZone{
|
||||||
Domain: zone.GetDomain(),
|
Domain: zone.GetDomain(),
|
||||||
SearchDomainDisabled: zone.GetSearchDomainDisabled(),
|
|
||||||
SkipPTRProcess: zone.GetSkipPTRProcess(),
|
|
||||||
}
|
}
|
||||||
for _, record := range zone.Records {
|
for _, record := range zone.Records {
|
||||||
dnsRecord := nbdns.SimpleRecord{
|
dnsRecord := nbdns.SimpleRecord{
|
||||||
@@ -1377,11 +1451,6 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
// Check context INSIDE lock to ensure atomicity with shutdown
|
|
||||||
if e.ctx.Err() != nil {
|
|
||||||
return e.ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, ok := e.peerStore.PeerConn(msg.Key)
|
conn, ok := e.peerStore.PeerConn(msg.Key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||||
@@ -1801,8 +1870,8 @@ func (e *Engine) stopDNSServer() {
|
|||||||
|
|
||||||
// SetSyncResponsePersistence enables or disables sync response persistence
|
// SetSyncResponsePersistence enables or disables sync response persistence
|
||||||
func (e *Engine) SetSyncResponsePersistence(enabled bool) {
|
func (e *Engine) SetSyncResponsePersistence(enabled bool) {
|
||||||
e.syncMsgMux.Lock()
|
e.syncRespMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncRespMux.Unlock()
|
||||||
|
|
||||||
if enabled == e.persistSyncResponse {
|
if enabled == e.persistSyncResponse {
|
||||||
return
|
return
|
||||||
@@ -1817,20 +1886,22 @@ func (e *Engine) SetSyncResponsePersistence(enabled bool) {
|
|||||||
|
|
||||||
// GetLatestSyncResponse returns the stored sync response if persistence is enabled
|
// GetLatestSyncResponse returns the stored sync response if persistence is enabled
|
||||||
func (e *Engine) GetLatestSyncResponse() (*mgmProto.SyncResponse, error) {
|
func (e *Engine) GetLatestSyncResponse() (*mgmProto.SyncResponse, error) {
|
||||||
e.syncMsgMux.Lock()
|
e.syncRespMux.RLock()
|
||||||
defer e.syncMsgMux.Unlock()
|
enabled := e.persistSyncResponse
|
||||||
|
latest := e.latestSyncResponse
|
||||||
|
e.syncRespMux.RUnlock()
|
||||||
|
|
||||||
if !e.persistSyncResponse {
|
if !enabled {
|
||||||
return nil, errors.New("sync response persistence is disabled")
|
return nil, errors.New("sync response persistence is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.latestSyncResponse == nil {
|
if latest == nil {
|
||||||
//nolint:nilnil
|
//nolint:nilnil
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Retrieving latest sync response with size %d bytes", proto.Size(e.latestSyncResponse))
|
log.Debugf("Retrieving latest sync response with size %d bytes", proto.Size(latest))
|
||||||
sr, ok := proto.Clone(e.latestSyncResponse).(*mgmProto.SyncResponse)
|
sr, ok := proto.Clone(latest).(*mgmProto.SyncResponse)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to clone sync response")
|
return nil, fmt.Errorf("failed to clone sync response")
|
||||||
}
|
}
|
||||||
@@ -1846,18 +1917,6 @@ func (e *Engine) GetWgAddr() netip.Addr {
|
|||||||
return e.wgInterface.Address().IP
|
return e.wgInterface.Address().IP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) RenewTun(fd int) error {
|
|
||||||
e.syncMsgMux.Lock()
|
|
||||||
wgInterface := e.wgInterface
|
|
||||||
e.syncMsgMux.Unlock()
|
|
||||||
|
|
||||||
if wgInterface == nil {
|
|
||||||
return fmt.Errorf("wireguard interface not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
return wgInterface.RenewTun(fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateDNSForwarder start or stop the DNS forwarder based on the domains and the feature flag
|
// updateDNSForwarder start or stop the DNS forwarder based on the domains and the feature flag
|
||||||
func (e *Engine) updateDNSForwarder(
|
func (e *Engine) updateDNSForwarder(
|
||||||
enabled bool,
|
enabled bool,
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
|
||||||
"github.com/netbirdio/netbird/shared/management/client"
|
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensures handleSync exits early when SkipNetworkMapUpdate is true
|
|
||||||
func TestEngine_HandleSync_SkipNetworkMapUpdate(t *testing.T) {
|
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
engine := NewEngine(ctx, cancel, nil, &client.MockClient{}, nil, &EngineConfig{
|
|
||||||
WgIfaceName: "utun199",
|
|
||||||
WgAddr: "100.70.0.1/24",
|
|
||||||
WgPrivateKey: key,
|
|
||||||
WgPort: 33100,
|
|
||||||
MTU: iface.DefaultMTU,
|
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
|
||||||
engine.ctx = ctx
|
|
||||||
|
|
||||||
// Precondition
|
|
||||||
if engine.networkSerial != 0 {
|
|
||||||
t.Fatalf("unexpected initial serial: %d", engine.networkSerial)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &mgmtProto.SyncResponse{
|
|
||||||
NetworkMap: &mgmtProto.NetworkMap{Serial: 42},
|
|
||||||
SkipNetworkMapUpdate: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := engine.handleSync(resp); err != nil {
|
|
||||||
t.Fatalf("handleSync returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if engine.networkSerial != 0 {
|
|
||||||
t.Fatalf("networkSerial changed despite SkipNetworkMapUpdate; got %d, want 0", engine.networkSerial)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensures handleSync exits early when NetworkMap is nil
|
|
||||||
func TestEngine_HandleSync_NilNetworkMap(t *testing.T) {
|
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
engine := NewEngine(ctx, cancel, nil, &client.MockClient{}, nil, &EngineConfig{
|
|
||||||
WgIfaceName: "utun198",
|
|
||||||
WgAddr: "100.70.0.2/24",
|
|
||||||
WgPrivateKey: key,
|
|
||||||
WgPort: 33101,
|
|
||||||
MTU: iface.DefaultMTU,
|
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
|
||||||
engine.ctx = ctx
|
|
||||||
|
|
||||||
resp := &mgmtProto.SyncResponse{NetworkMap: nil}
|
|
||||||
|
|
||||||
if err := engine.handleSync(resp); err != nil {
|
|
||||||
t.Fatalf("handleSync returned error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -25,17 +25,17 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers"
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
|
|
||||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||||
"github.com/netbirdio/netbird/management/server/groups"
|
"github.com/netbirdio/netbird/management/server/groups"
|
||||||
|
"github.com/netbirdio/netbird/management/server/peers/ephemeral/manager"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
@@ -55,6 +55,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
|
"github.com/netbirdio/netbird/management/server/peers"
|
||||||
"github.com/netbirdio/netbird/management/server/permissions"
|
"github.com/netbirdio/netbird/management/server/permissions"
|
||||||
"github.com/netbirdio/netbird/management/server/settings"
|
"github.com/netbirdio/netbird/management/server/settings"
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
@@ -110,10 +111,6 @@ type MockWGIface struct {
|
|||||||
LastActivitiesFunc func() map[string]monotime.Time
|
LastActivitiesFunc func() map[string]monotime.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockWGIface) RenewTun(_ int) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) RemoveEndpointAddress(_ string) error {
|
func (m *MockWGIface) RemoveEndpointAddress(_ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -252,7 +249,7 @@ func TestEngine_SSH(t *testing.T) {
|
|||||||
},
|
},
|
||||||
MobileDependency{},
|
MobileDependency{},
|
||||||
peer.NewRecorder("https://mgm"),
|
peer.NewRecorder("https://mgm"),
|
||||||
nil,
|
nil, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
engine.dnsServer = &dns.MockServer{
|
engine.dnsServer = &dns.MockServer{
|
||||||
@@ -414,21 +411,13 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||||
engine := NewEngine(
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||||
ctx, cancel,
|
WgIfaceName: "utun102",
|
||||||
&signal.MockClient{},
|
WgAddr: "100.64.0.1/24",
|
||||||
&mgmt.MockClient{},
|
WgPrivateKey: key,
|
||||||
relayMgr,
|
WgPort: 33100,
|
||||||
&EngineConfig{
|
MTU: iface.DefaultMTU,
|
||||||
WgIfaceName: "utun102",
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
WgAddr: "100.64.0.1/24",
|
|
||||||
WgPrivateKey: key,
|
|
||||||
WgPort: 33100,
|
|
||||||
MTU: iface.DefaultMTU,
|
|
||||||
},
|
|
||||||
MobileDependency{},
|
|
||||||
peer.NewRecorder("https://mgm"),
|
|
||||||
nil)
|
|
||||||
|
|
||||||
wgIface := &MockWGIface{
|
wgIface := &MockWGIface{
|
||||||
NameFunc: func() string { return "utun102" },
|
NameFunc: func() string { return "utun102" },
|
||||||
@@ -631,7 +620,7 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
// feed updates to Engine via mocked Management client
|
// feed updates to Engine via mocked Management client
|
||||||
updates := make(chan *mgmtProto.SyncResponse)
|
updates := make(chan *mgmtProto.SyncResponse)
|
||||||
defer close(updates)
|
defer close(updates)
|
||||||
syncFunc := func(ctx context.Context, info *system.Info, networkSerial uint64, msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
syncFunc := func(ctx context.Context, info *system.Info, msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
||||||
for msg := range updates {
|
for msg := range updates {
|
||||||
err := msgHandler(msg)
|
err := msgHandler(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -647,7 +636,7 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
MTU: iface.DefaultMTU,
|
MTU: iface.DefaultMTU,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
engine.ctx = ctx
|
engine.ctx = ctx
|
||||||
|
|
||||||
engine.dnsServer = &dns.MockServer{
|
engine.dnsServer = &dns.MockServer{
|
||||||
@@ -812,7 +801,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
MTU: iface.DefaultMTU,
|
MTU: iface.DefaultMTU,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
engine.ctx = ctx
|
engine.ctx = ctx
|
||||||
newNet, err := stdnet.NewNet(context.Background(), nil)
|
newNet, err := stdnet.NewNet(context.Background(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1014,7 +1003,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
MTU: iface.DefaultMTU,
|
MTU: iface.DefaultMTU,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
engine.ctx = ctx
|
engine.ctx = ctx
|
||||||
|
|
||||||
newNet, err := stdnet.NewNet(context.Background(), nil)
|
newNet, err := stdnet.NewNet(context.Background(), nil)
|
||||||
@@ -1540,7 +1529,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||||
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, relayMgr, conf, MobileDependency{}, peer.NewRecorder("https://mgm"), nil), nil
|
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, relayMgr, conf, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil), nil
|
||||||
e.ctx = ctx
|
e.ctx = ctx
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
@@ -1599,6 +1588,7 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
}
|
}
|
||||||
t.Cleanup(cleanUp)
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
|
jobManager := job.NewJobManager(nil, store)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -1628,17 +1618,14 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
|
|
||||||
updateManager := update_channel.NewPeersUpdateManager(metrics)
|
updateManager := update_channel.NewPeersUpdateManager(metrics)
|
||||||
requestBuffer := server.NewAccountRequestBuffer(context.Background(), store)
|
requestBuffer := server.NewAccountRequestBuffer(context.Background(), store)
|
||||||
networkMapController := controller.NewController(context.Background(), store, metrics, updateManager, requestBuffer, server.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), manager.NewEphemeralManager(store, peersManager), config)
|
networkMapController := controller.NewController(context.Background(), store, metrics, updateManager, requestBuffer, server.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock())
|
||||||
accountManager, err := server.BuildManager(context.Background(), config, store, networkMapController, nil, "", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
|
accountManager, err := server.BuildManager(context.Background(), config, store, networkMapController, jobManager, nil, "", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsManager, err := nbgrpc.NewTimeBasedAuthSecretsManager(updateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
secretsManager := nbgrpc.NewTimeBasedAuthSecretsManager(updateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
||||||
if err != nil {
|
mgmtServer, err := nbgrpc.NewServer(config, accountManager, settingsMockManager, updateManager, jobManager, secretsManager, nil, &manager.EphemeralManager{}, nil, &server.MockIntegratedValidator{}, networkMapController)
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
mgmtServer, err := nbgrpc.NewServer(config, accountManager, settingsMockManager, secretsManager, nil, nil, &server.MockIntegratedValidator{}, networkMapController)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
type wgIfaceBase interface {
|
type wgIfaceBase interface {
|
||||||
Create() error
|
Create() error
|
||||||
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
||||||
RenewTun(fd int) error
|
|
||||||
IsUserspaceBind() bool
|
IsUserspaceBind() bool
|
||||||
Name() string
|
Name() string
|
||||||
Address() wgaddr.Address
|
Address() wgaddr.Address
|
||||||
|
|||||||
@@ -1,218 +0,0 @@
|
|||||||
//go:build darwin && !ios
|
|
||||||
|
|
||||||
package sleep
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -framework IOKit -framework CoreFoundation
|
|
||||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
|
||||||
#include <IOKit/IOMessage.h>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
|
|
||||||
extern void sleepCallbackBridge();
|
|
||||||
extern void poweredOnCallbackBridge();
|
|
||||||
extern void suspendedCallbackBridge();
|
|
||||||
extern void resumedCallbackBridge();
|
|
||||||
|
|
||||||
|
|
||||||
// C global variables for IOKit state
|
|
||||||
static IONotificationPortRef g_notifyPortRef = NULL;
|
|
||||||
static io_object_t g_notifierObject = 0;
|
|
||||||
static io_object_t g_generalInterestNotifier = 0;
|
|
||||||
static io_connect_t g_rootPort = 0;
|
|
||||||
static CFRunLoopRef g_runLoop = NULL;
|
|
||||||
|
|
||||||
static void sleepCallback(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) {
|
|
||||||
switch (messageType) {
|
|
||||||
case kIOMessageSystemWillSleep:
|
|
||||||
sleepCallbackBridge();
|
|
||||||
IOAllowPowerChange(g_rootPort, (long)messageArgument);
|
|
||||||
break;
|
|
||||||
case kIOMessageSystemHasPoweredOn:
|
|
||||||
poweredOnCallbackBridge();
|
|
||||||
break;
|
|
||||||
case kIOMessageServiceIsSuspended:
|
|
||||||
suspendedCallbackBridge();
|
|
||||||
break;
|
|
||||||
case kIOMessageServiceIsResumed:
|
|
||||||
resumedCallbackBridge();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void registerNotifications() {
|
|
||||||
g_rootPort = IORegisterForSystemPower(
|
|
||||||
NULL,
|
|
||||||
&g_notifyPortRef,
|
|
||||||
(IOServiceInterestCallback)sleepCallback,
|
|
||||||
&g_notifierObject
|
|
||||||
);
|
|
||||||
|
|
||||||
if (g_rootPort == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CFRunLoopAddSource(CFRunLoopGetCurrent(),
|
|
||||||
IONotificationPortGetRunLoopSource(g_notifyPortRef),
|
|
||||||
kCFRunLoopCommonModes);
|
|
||||||
|
|
||||||
g_runLoop = CFRunLoopGetCurrent();
|
|
||||||
CFRunLoopRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unregisterNotifications() {
|
|
||||||
CFRunLoopRemoveSource(g_runLoop,
|
|
||||||
IONotificationPortGetRunLoopSource(g_notifyPortRef),
|
|
||||||
kCFRunLoopCommonModes);
|
|
||||||
|
|
||||||
IODeregisterForSystemPower(&g_notifierObject);
|
|
||||||
IOServiceClose(g_rootPort);
|
|
||||||
IONotificationPortDestroy(g_notifyPortRef);
|
|
||||||
CFRunLoopStop(g_runLoop);
|
|
||||||
|
|
||||||
g_notifyPortRef = NULL;
|
|
||||||
g_notifierObject = 0;
|
|
||||||
g_rootPort = 0;
|
|
||||||
g_runLoop = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
serviceRegistry = make(map[*Detector]struct{})
|
|
||||||
serviceRegistryMu sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
//export sleepCallbackBridge
|
|
||||||
func sleepCallbackBridge() {
|
|
||||||
log.Info("sleepCallbackBridge event triggered")
|
|
||||||
|
|
||||||
serviceRegistryMu.Lock()
|
|
||||||
defer serviceRegistryMu.Unlock()
|
|
||||||
|
|
||||||
for svc := range serviceRegistry {
|
|
||||||
svc.triggerCallback(EventTypeSleep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//export resumedCallbackBridge
|
|
||||||
func resumedCallbackBridge() {
|
|
||||||
log.Info("resumedCallbackBridge event triggered")
|
|
||||||
}
|
|
||||||
|
|
||||||
//export suspendedCallbackBridge
|
|
||||||
func suspendedCallbackBridge() {
|
|
||||||
log.Info("suspendedCallbackBridge event triggered")
|
|
||||||
}
|
|
||||||
|
|
||||||
//export poweredOnCallbackBridge
|
|
||||||
func poweredOnCallbackBridge() {
|
|
||||||
log.Info("poweredOnCallbackBridge event triggered")
|
|
||||||
serviceRegistryMu.Lock()
|
|
||||||
defer serviceRegistryMu.Unlock()
|
|
||||||
|
|
||||||
for svc := range serviceRegistry {
|
|
||||||
svc.triggerCallback(EventTypeWakeUp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Detector struct {
|
|
||||||
callback func(event EventType)
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDetector() (*Detector, error) {
|
|
||||||
return &Detector{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Detector) Register(callback func(event EventType)) error {
|
|
||||||
serviceRegistryMu.Lock()
|
|
||||||
defer serviceRegistryMu.Unlock()
|
|
||||||
|
|
||||||
if _, exists := serviceRegistry[d]; exists {
|
|
||||||
return fmt.Errorf("detector service already registered")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.callback = callback
|
|
||||||
|
|
||||||
d.ctx, d.cancel = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
if len(serviceRegistry) > 0 {
|
|
||||||
serviceRegistry[d] = struct{}{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceRegistry[d] = struct{}{}
|
|
||||||
|
|
||||||
// CFRunLoop must run on a single fixed OS thread
|
|
||||||
go func() {
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
C.registerNotifications()
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Info("sleep detection service started on macOS")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deregister removes the detector. When the last detector is removed, IOKit registration is torn down
|
|
||||||
// and the runloop is stopped and cleaned up.
|
|
||||||
func (d *Detector) Deregister() error {
|
|
||||||
serviceRegistryMu.Lock()
|
|
||||||
defer serviceRegistryMu.Unlock()
|
|
||||||
_, exists := serviceRegistry[d]
|
|
||||||
if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel and remove this detector
|
|
||||||
d.cancel()
|
|
||||||
delete(serviceRegistry, d)
|
|
||||||
|
|
||||||
// If other Detectors still exist, leave IOKit running
|
|
||||||
if len(serviceRegistry) > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("sleep detection service stopping (deregister)")
|
|
||||||
|
|
||||||
// Deregister IOKit notifications, stop runloop, and free resources
|
|
||||||
C.unregisterNotifications()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Detector) triggerCallback(event EventType) {
|
|
||||||
doneChan := make(chan struct{})
|
|
||||||
|
|
||||||
timeout := time.NewTimer(500 * time.Millisecond)
|
|
||||||
defer timeout.Stop()
|
|
||||||
|
|
||||||
cb := d.callback
|
|
||||||
go func(callback func(event EventType)) {
|
|
||||||
log.Info("sleep detection event fired")
|
|
||||||
callback(event)
|
|
||||||
close(doneChan)
|
|
||||||
}(cb)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-doneChan:
|
|
||||||
case <-d.ctx.Done():
|
|
||||||
case <-timeout.C:
|
|
||||||
log.Warnf("sleep callback timed out")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !darwin || ios
|
|
||||||
|
|
||||||
package sleep
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func NewDetector() (detector, error) {
|
|
||||||
return nil, fmt.Errorf("sleep not supported on this platform")
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package sleep
|
|
||||||
|
|
||||||
var (
|
|
||||||
EventTypeUnknown EventType = 0
|
|
||||||
EventTypeSleep EventType = 1
|
|
||||||
EventTypeWakeUp EventType = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventType int
|
|
||||||
|
|
||||||
type detector interface {
|
|
||||||
Register(callback func(eventType EventType)) error
|
|
||||||
Deregister() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
detector detector
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() (*Service, error) {
|
|
||||||
d, err := NewDetector()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Service{
|
|
||||||
detector: d,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Register(callback func(eventType EventType)) error {
|
|
||||||
return s.detector.Register(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Deregister() error {
|
|
||||||
return s.detector.Deregister()
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -93,8 +90,7 @@ func NewClient(cfgFile, stateFile, deviceName string, osVersion string, osName s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run start the internal client. It is a blocker function
|
// Run start the internal client. It is a blocker function
|
||||||
func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error {
|
func (c *Client) Run(fd int32, interfaceName string) error {
|
||||||
exportEnvList(envList)
|
|
||||||
log.Infof("Starting NetBird client")
|
log.Infof("Starting NetBird client")
|
||||||
log.Debugf("Tunnel uses interface: %s", interfaceName)
|
log.Debugf("Tunnel uses interface: %s", interfaceName)
|
||||||
cfg, err := profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
cfg, err := profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
||||||
@@ -232,7 +228,7 @@ func (c *Client) LoginForMobile() string {
|
|||||||
ConfigPath: c.cfgFile,
|
ConfigPath: c.cfgFile,
|
||||||
})
|
})
|
||||||
|
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, cfg, false, false, "")
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, cfg, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
@@ -437,19 +433,3 @@ func toNetIDs(routes []string) []route.NetID {
|
|||||||
}
|
}
|
||||||
return netIDs
|
return netIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportEnvList(list *EnvList) {
|
|
||||||
if list == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for k, v := range list.AllItems() {
|
|
||||||
log.Debugf("Env variable %s's value is currently: %s", k, os.Getenv(k))
|
|
||||||
log.Debugf("Setting env variable %s: %s", k, v)
|
|
||||||
|
|
||||||
if err := os.Setenv(k, v); err != nil {
|
|
||||||
log.Errorf("could not set env variable %s: %v", k, err)
|
|
||||||
} else {
|
|
||||||
log.Debugf("Env variable %s was set successfully", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
|
||||||
|
|
||||||
import "github.com/netbirdio/netbird/client/internal/peer"
|
|
||||||
|
|
||||||
// EnvList is an exported struct to be bound by gomobile
|
|
||||||
type EnvList struct {
|
|
||||||
data map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEnvList creates a new EnvList
|
|
||||||
func NewEnvList() *EnvList {
|
|
||||||
return &EnvList{data: make(map[string]string)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put adds a key-value pair
|
|
||||||
func (el *EnvList) Put(key, value string) {
|
|
||||||
el.data[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get retrieves a value by key
|
|
||||||
func (el *EnvList) Get(key string) string {
|
|
||||||
return el.data[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (el *EnvList) AllItems() map[string]string {
|
|
||||||
return el.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEnvKeyNBForceRelay Exports the environment variable for the iOS client
|
|
||||||
func GetEnvKeyNBForceRelay() string {
|
|
||||||
return peer.EnvKeyNBForceRelay
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
import _ "golang.org/x/mobile/bind"
|
import _ "golang.org/x/mobile/bind"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
// PeerInfo describe information about the peers. It designed for the UI usage
|
// PeerInfo describe information about the peers. It designed for the UI usage
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package NetBirdSDK
|
package NetBirdSDK
|
||||||
|
|
||||||
// RoutesSelectionInfoCollection made for Java layer to get non default types as collection
|
// RoutesSelectionInfoCollection made for Java layer to get non default types as collection
|
||||||
|
|||||||
66
client/jobexec/executor.go
Normal file
66
client/jobexec/executor.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package jobexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/debug"
|
||||||
|
"github.com/netbirdio/netbird/upload-server/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxBundleWaitTime = 60 * time.Minute // maximum wait time for bundle generation (1 hour)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrJobNotImplemented = errors.New("job not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Executor struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExecutor() *Executor {
|
||||||
|
return &Executor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Executor) BundleJob(ctx context.Context, debugBundleDependencies debug.GeneratorDependencies, params debug.BundleConfig, waitForDuration time.Duration, mgmURL string) (string, error) {
|
||||||
|
if waitForDuration > MaxBundleWaitTime {
|
||||||
|
log.Warnf("bundle wait time %v exceeds maximum %v, capping to maximum", waitForDuration, MaxBundleWaitTime)
|
||||||
|
waitForDuration = MaxBundleWaitTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if waitForDuration > 0 {
|
||||||
|
waitFor(ctx, waitForDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("execute debug bundle generation")
|
||||||
|
|
||||||
|
bundleGenerator := debug.NewBundleGenerator(debugBundleDependencies, params)
|
||||||
|
|
||||||
|
path, err := bundleGenerator.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("generate debug bundle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := debug.UploadDebugBundle(ctx, types.DefaultBundleURL, mgmURL, path)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to upload debug bundle: %v", err)
|
||||||
|
return "", fmt.Errorf("upload debug bundle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("debug bundle has been generated well")
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitFor(ctx context.Context, duration time.Duration) {
|
||||||
|
log.Infof("wait for %v minutes before executing debug bundle", duration.Minutes())
|
||||||
|
select {
|
||||||
|
case <-time.After(duration):
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Infof("wait cancelled: %v", ctx.Err())
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ service DaemonService {
|
|||||||
// Status of the service.
|
// Status of the service.
|
||||||
rpc Status(StatusRequest) returns (StatusResponse) {}
|
rpc Status(StatusRequest) returns (StatusResponse) {}
|
||||||
|
|
||||||
// Down stops engine work in the daemon.
|
// Down engine work in the daemon.
|
||||||
rpc Down(DownRequest) returns (DownResponse) {}
|
rpc Down(DownRequest) returns (DownResponse) {}
|
||||||
|
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
@@ -93,26 +93,9 @@ service DaemonService {
|
|||||||
|
|
||||||
// WaitJWTToken waits for JWT authentication completion
|
// WaitJWTToken waits for JWT authentication completion
|
||||||
rpc WaitJWTToken(WaitJWTTokenRequest) returns (WaitJWTTokenResponse) {}
|
rpc WaitJWTToken(WaitJWTTokenRequest) returns (WaitJWTTokenResponse) {}
|
||||||
|
|
||||||
rpc NotifyOSLifecycle(OSLifecycleRequest) returns(OSLifecycleResponse) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
message OSLifecycleRequest {
|
|
||||||
// avoid collision with loglevel enum
|
|
||||||
enum CycleType {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
SLEEP = 1;
|
|
||||||
WAKEUP = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
CycleType type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OSLifecycleResponse {}
|
|
||||||
|
|
||||||
|
|
||||||
message LoginRequest {
|
message LoginRequest {
|
||||||
// setupKey netbird setup key.
|
// setupKey netbird setup key.
|
||||||
string setupKey = 1;
|
string setupKey = 1;
|
||||||
@@ -451,7 +434,6 @@ message ForwardingRulesResponse {
|
|||||||
// DebugBundler
|
// DebugBundler
|
||||||
message DebugBundleRequest {
|
message DebugBundleRequest {
|
||||||
bool anonymize = 1;
|
bool anonymize = 1;
|
||||||
string status = 2;
|
|
||||||
bool systemInfo = 3;
|
bool systemInfo = 3;
|
||||||
string uploadURL = 4;
|
string uploadURL = 4;
|
||||||
uint32 logFileCount = 5;
|
uint32 logFileCount = 5;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type DaemonServiceClient interface {
|
|||||||
Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error)
|
Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error)
|
||||||
// Status of the service.
|
// Status of the service.
|
||||||
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
|
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
|
||||||
// Down stops engine work in the daemon.
|
// Down engine work in the daemon.
|
||||||
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
|
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error)
|
GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error)
|
||||||
@@ -70,7 +70,6 @@ type DaemonServiceClient interface {
|
|||||||
RequestJWTAuth(ctx context.Context, in *RequestJWTAuthRequest, opts ...grpc.CallOption) (*RequestJWTAuthResponse, error)
|
RequestJWTAuth(ctx context.Context, in *RequestJWTAuthRequest, opts ...grpc.CallOption) (*RequestJWTAuthResponse, error)
|
||||||
// WaitJWTToken waits for JWT authentication completion
|
// WaitJWTToken waits for JWT authentication completion
|
||||||
WaitJWTToken(ctx context.Context, in *WaitJWTTokenRequest, opts ...grpc.CallOption) (*WaitJWTTokenResponse, error)
|
WaitJWTToken(ctx context.Context, in *WaitJWTTokenRequest, opts ...grpc.CallOption) (*WaitJWTTokenResponse, error)
|
||||||
NotifyOSLifecycle(ctx context.Context, in *OSLifecycleRequest, opts ...grpc.CallOption) (*OSLifecycleResponse, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type daemonServiceClient struct {
|
type daemonServiceClient struct {
|
||||||
@@ -383,15 +382,6 @@ func (c *daemonServiceClient) WaitJWTToken(ctx context.Context, in *WaitJWTToken
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *daemonServiceClient) NotifyOSLifecycle(ctx context.Context, in *OSLifecycleRequest, opts ...grpc.CallOption) (*OSLifecycleResponse, error) {
|
|
||||||
out := new(OSLifecycleResponse)
|
|
||||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/NotifyOSLifecycle", in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonServiceServer is the server API for DaemonService service.
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
// All implementations must embed UnimplementedDaemonServiceServer
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -405,7 +395,7 @@ type DaemonServiceServer interface {
|
|||||||
Up(context.Context, *UpRequest) (*UpResponse, error)
|
Up(context.Context, *UpRequest) (*UpResponse, error)
|
||||||
// Status of the service.
|
// Status of the service.
|
||||||
Status(context.Context, *StatusRequest) (*StatusResponse, error)
|
Status(context.Context, *StatusRequest) (*StatusResponse, error)
|
||||||
// Down stops engine work in the daemon.
|
// Down engine work in the daemon.
|
||||||
Down(context.Context, *DownRequest) (*DownResponse, error)
|
Down(context.Context, *DownRequest) (*DownResponse, error)
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error)
|
GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error)
|
||||||
@@ -448,7 +438,6 @@ type DaemonServiceServer interface {
|
|||||||
RequestJWTAuth(context.Context, *RequestJWTAuthRequest) (*RequestJWTAuthResponse, error)
|
RequestJWTAuth(context.Context, *RequestJWTAuthRequest) (*RequestJWTAuthResponse, error)
|
||||||
// WaitJWTToken waits for JWT authentication completion
|
// WaitJWTToken waits for JWT authentication completion
|
||||||
WaitJWTToken(context.Context, *WaitJWTTokenRequest) (*WaitJWTTokenResponse, error)
|
WaitJWTToken(context.Context, *WaitJWTTokenRequest) (*WaitJWTTokenResponse, error)
|
||||||
NotifyOSLifecycle(context.Context, *OSLifecycleRequest) (*OSLifecycleResponse, error)
|
|
||||||
mustEmbedUnimplementedDaemonServiceServer()
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,9 +538,6 @@ func (UnimplementedDaemonServiceServer) RequestJWTAuth(context.Context, *Request
|
|||||||
func (UnimplementedDaemonServiceServer) WaitJWTToken(context.Context, *WaitJWTTokenRequest) (*WaitJWTTokenResponse, error) {
|
func (UnimplementedDaemonServiceServer) WaitJWTToken(context.Context, *WaitJWTTokenRequest) (*WaitJWTTokenResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method WaitJWTToken not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method WaitJWTToken not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDaemonServiceServer) NotifyOSLifecycle(context.Context, *OSLifecycleRequest) (*OSLifecycleResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method NotifyOSLifecycle not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
|
|
||||||
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -1126,24 +1112,6 @@ func _DaemonService_WaitJWTToken_Handler(srv interface{}, ctx context.Context, d
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _DaemonService_NotifyOSLifecycle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(OSLifecycleRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(DaemonServiceServer).NotifyOSLifecycle(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/daemon.DaemonService/NotifyOSLifecycle",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(DaemonServiceServer).NotifyOSLifecycle(ctx, req.(*OSLifecycleRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -1271,10 +1239,6 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "WaitJWTToken",
|
MethodName: "WaitJWTToken",
|
||||||
Handler: _DaemonService_WaitJWTToken_Handler,
|
Handler: _DaemonService_WaitJWTToken_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "NotifyOSLifecycle",
|
|
||||||
Handler: _DaemonService_NotifyOSLifecycle_Handler,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,24 +4,16 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/debug"
|
"github.com/netbirdio/netbird/client/internal/debug"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/upload-server/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxBundleUploadSize = 50 * 1024 * 1024
|
|
||||||
|
|
||||||
// DebugBundle creates a debug bundle and returns the location.
|
// DebugBundle creates a debug bundle and returns the location.
|
||||||
func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (resp *proto.DebugBundleResponse, err error) {
|
func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (resp *proto.DebugBundleResponse, err error) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
@@ -37,11 +29,10 @@ func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (
|
|||||||
InternalConfig: s.config,
|
InternalConfig: s.config,
|
||||||
StatusRecorder: s.statusRecorder,
|
StatusRecorder: s.statusRecorder,
|
||||||
SyncResponse: syncResponse,
|
SyncResponse: syncResponse,
|
||||||
LogFile: s.logFile,
|
LogPath: s.logFile,
|
||||||
},
|
},
|
||||||
debug.BundleConfig{
|
debug.BundleConfig{
|
||||||
Anonymize: req.GetAnonymize(),
|
Anonymize: req.GetAnonymize(),
|
||||||
ClientStatus: req.GetStatus(),
|
|
||||||
IncludeSystemInfo: req.GetSystemInfo(),
|
IncludeSystemInfo: req.GetSystemInfo(),
|
||||||
LogFileCount: req.GetLogFileCount(),
|
LogFileCount: req.GetLogFileCount(),
|
||||||
},
|
},
|
||||||
@@ -55,7 +46,7 @@ func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (
|
|||||||
if req.GetUploadURL() == "" {
|
if req.GetUploadURL() == "" {
|
||||||
return &proto.DebugBundleResponse{Path: path}, nil
|
return &proto.DebugBundleResponse{Path: path}, nil
|
||||||
}
|
}
|
||||||
key, err := uploadDebugBundle(context.Background(), req.GetUploadURL(), s.config.ManagementURL.String(), path)
|
key, err := debug.UploadDebugBundle(context.Background(), req.GetUploadURL(), s.config.ManagementURL.String(), path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to upload debug bundle to %s: %v", req.GetUploadURL(), err)
|
log.Errorf("failed to upload debug bundle to %s: %v", req.GetUploadURL(), err)
|
||||||
return &proto.DebugBundleResponse{Path: path, UploadFailureReason: err.Error()}, nil
|
return &proto.DebugBundleResponse{Path: path, UploadFailureReason: err.Error()}, nil
|
||||||
@@ -66,92 +57,6 @@ func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (
|
|||||||
return &proto.DebugBundleResponse{Path: path, UploadedKey: key}, nil
|
return &proto.DebugBundleResponse{Path: path, UploadedKey: key}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadDebugBundle(ctx context.Context, url, managementURL, filePath string) (key string, err error) {
|
|
||||||
response, err := getUploadURL(ctx, url, managementURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = upload(ctx, filePath, response)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return response.Key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func upload(ctx context.Context, filePath string, response *types.GetURLResponse) error {
|
|
||||||
fileData, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("open file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer fileData.Close()
|
|
||||||
|
|
||||||
stat, err := fileData.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("stat file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat.Size() > maxBundleUploadSize {
|
|
||||||
return fmt.Errorf("file size exceeds maximum limit of %d bytes", maxBundleUploadSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "PUT", response.URL, fileData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create PUT request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.ContentLength = stat.Size()
|
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
|
||||||
|
|
||||||
putResp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("upload failed: %v", err)
|
|
||||||
}
|
|
||||||
defer putResp.Body.Close()
|
|
||||||
|
|
||||||
if putResp.StatusCode != http.StatusOK {
|
|
||||||
body, _ := io.ReadAll(putResp.Body)
|
|
||||||
return fmt.Errorf("upload status %d: %s", putResp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUploadURL(ctx context.Context, url string, managementURL string) (*types.GetURLResponse, error) {
|
|
||||||
id := getURLHash(managementURL)
|
|
||||||
getReq, err := http.NewRequestWithContext(ctx, "GET", url+"?id="+id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create GET request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
getReq.Header.Set(types.ClientHeader, types.ClientHeaderValue)
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(getReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get presigned URL: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
return nil, fmt.Errorf("get presigned URL status %d: %s", resp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
urlBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read response body: %w", err)
|
|
||||||
}
|
|
||||||
var response types.GetURLResponse
|
|
||||||
if err := json.Unmarshal(urlBytes, &response); err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshal response: %w", err)
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURLHash(url string) string {
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogLevel gets the current logging level for the server.
|
// GetLogLevel gets the current logging level for the server.
|
||||||
func (s *Server) GetLogLevel(_ context.Context, _ *proto.GetLogLevelRequest) (*proto.GetLogLevelResponse, error) {
|
func (s *Server) GetLogLevel(_ context.Context, _ *proto.GetLogLevelRequest) (*proto.GetLogLevelResponse, error) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NotifyOSLifecycle handles operating system lifecycle events by executing appropriate logic based on the request type.
|
|
||||||
func (s *Server) NotifyOSLifecycle(callerCtx context.Context, req *proto.OSLifecycleRequest) (*proto.OSLifecycleResponse, error) {
|
|
||||||
switch req.GetType() {
|
|
||||||
case proto.OSLifecycleRequest_WAKEUP:
|
|
||||||
return s.handleWakeUp(callerCtx)
|
|
||||||
case proto.OSLifecycleRequest_SLEEP:
|
|
||||||
return s.handleSleep(callerCtx)
|
|
||||||
default:
|
|
||||||
log.Errorf("unknown OSLifecycleRequest type: %v", req.GetType())
|
|
||||||
}
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleWakeUp processes a wake-up event by triggering the Up command if the system was previously put to sleep.
|
|
||||||
// It resets the sleep state and logs the process. Returns a response or an error if the Up command fails.
|
|
||||||
func (s *Server) handleWakeUp(callerCtx context.Context) (*proto.OSLifecycleResponse, error) {
|
|
||||||
if !s.sleepTriggeredDown.Load() {
|
|
||||||
log.Info("skipping up because wasn't sleep down")
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid other wakeup runs if sleep didn't make the computer sleep
|
|
||||||
s.sleepTriggeredDown.Store(false)
|
|
||||||
|
|
||||||
log.Info("running up after wake up")
|
|
||||||
_, err := s.Up(callerCtx, &proto.UpRequest{})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("running up failed: %v", err)
|
|
||||||
return &proto.OSLifecycleResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("running up command executed successfully")
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSleep handles the sleep event by initiating a "down" sequence if the system is in a connected or connecting state.
|
|
||||||
func (s *Server) handleSleep(callerCtx context.Context) (*proto.OSLifecycleResponse, error) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
status, err := state.Status()
|
|
||||||
if err != nil {
|
|
||||||
s.mutex.Unlock()
|
|
||||||
return &proto.OSLifecycleResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if status != internal.StatusConnecting && status != internal.StatusConnected {
|
|
||||||
log.Infof("skipping setting the agent down because status is %s", status)
|
|
||||||
s.mutex.Unlock()
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
s.mutex.Unlock()
|
|
||||||
|
|
||||||
log.Info("running down after system started sleeping")
|
|
||||||
|
|
||||||
_, err = s.Down(callerCtx, &proto.DownRequest{})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("running down failed: %v", err)
|
|
||||||
return &proto.OSLifecycleResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
log.Info("running down executed successfully")
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestServer() *Server {
|
|
||||||
ctx := internal.CtxInitState(context.Background())
|
|
||||||
return &Server{
|
|
||||||
rootCtx: ctx,
|
|
||||||
statusRecorder: peer.NewRecorder(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_WakeUp_SkipsWhenNotSleepTriggered(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
// sleepTriggeredDown is false by default
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should remain false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SkipsWhenStatusIdle(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusIdle)
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should remain false when status is Idle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SkipsWhenStatusNeedsLogin(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusNeedsLogin)
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should remain false when status is NeedsLogin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SetsFlag_WhenConnecting(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusConnecting)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s.actCancel = cancel
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(ctx, &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp, "handleSleep returns not nil response on success")
|
|
||||||
assert.True(t, s.sleepTriggeredDown.Load(), "flag should be set after sleep when connecting")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SetsFlag_WhenConnected(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusConnected)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s.actCancel = cancel
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(ctx, &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp, "handleSleep returns not nil response on success")
|
|
||||||
assert.True(t, s.sleepTriggeredDown.Load(), "flag should be set after sleep when connected")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_WakeUp_ResetsFlag(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
// Manually set the flag to simulate prior sleep down
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
// WakeUp will try to call Up which fails without proper setup, but flag should reset first
|
|
||||||
_, _ = s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should be reset after WakeUp attempt")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_MultipleWakeUpCalls(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
// First wakeup without prior sleep - should be no-op
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
|
|
||||||
// Simulate prior sleep
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
// First wakeup after sleep - should reset flag
|
|
||||||
_, _ = s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
|
|
||||||
// Second wakeup - should be no-op
|
|
||||||
resp, err = s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleWakeUp_SkipsWhenFlagFalse(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
resp, err := s.handleWakeUp(context.Background())
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleWakeUp_ResetsFlagBeforeUp(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
// Even if Up fails, flag should be reset
|
|
||||||
_, _ = s.handleWakeUp(context.Background())
|
|
||||||
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag must be reset before calling Up")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleSleep_SkipsForNonActiveStates(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
status internal.StatusType
|
|
||||||
}{
|
|
||||||
{"Idle", internal.StatusIdle},
|
|
||||||
{"NeedsLogin", internal.StatusNeedsLogin},
|
|
||||||
{"LoginFailed", internal.StatusLoginFailed},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(tt.status)
|
|
||||||
|
|
||||||
resp, err := s.handleSleep(context.Background())
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleSleep_ProceedsForActiveStates(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
status internal.StatusType
|
|
||||||
}{
|
|
||||||
{"Connecting", internal.StatusConnecting},
|
|
||||||
{"Connected", internal.StatusConnected},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(tt.status)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s.actCancel = cancel
|
|
||||||
|
|
||||||
resp, err := s.handleSleep(ctx)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
assert.True(t, s.sleepTriggeredDown.Load())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,15 +13,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
@@ -32,6 +28,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,9 +82,6 @@ type Server struct {
|
|||||||
profilesDisabled bool
|
profilesDisabled bool
|
||||||
updateSettingsDisabled bool
|
updateSettingsDisabled bool
|
||||||
|
|
||||||
// sleepTriggeredDown holds a state indicated if the sleep handler triggered the last client down
|
|
||||||
sleepTriggeredDown atomic.Bool
|
|
||||||
|
|
||||||
jwtCache *jwtCache
|
jwtCache *jwtCache
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +501,7 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
if msg.Hint != nil {
|
if msg.Hint != nil {
|
||||||
hint = *msg.Hint
|
hint = *msg.Hint
|
||||||
}
|
}
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, msg.IsUnixDesktopClient, false, hint)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, msg.IsUnixDesktopClient, hint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Set(internal.StatusLoginFailed)
|
state.Set(internal.StatusLoginFailed)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -822,7 +816,6 @@ func (s *Server) Down(ctx context.Context, _ *proto.DownRequest) (*proto.DownRes
|
|||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
if err := s.cleanupConnection(); err != nil {
|
if err := s.cleanupConnection(); err != nil {
|
||||||
// todo review to update the status in case any type of error
|
|
||||||
log.Errorf("failed to shut down properly: %v", err)
|
log.Errorf("failed to shut down properly: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -915,7 +908,6 @@ func (s *Server) handleActiveProfileLogout(ctx context.Context) (*proto.LogoutRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.cleanupConnection(); err != nil && !errors.Is(err, ErrServiceNotUp) {
|
if err := s.cleanupConnection(); err != nil && !errors.Is(err, ErrServiceNotUp) {
|
||||||
// todo review to update the status in case any type of error
|
|
||||||
log.Errorf("failed to cleanup connection: %v", err)
|
log.Errorf("failed to cleanup connection: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1085,7 +1077,7 @@ func (s *Server) Status(
|
|||||||
if msg.GetFullPeerStatus {
|
if msg.GetFullPeerStatus {
|
||||||
s.runProbes(msg.ShouldRunProbes)
|
s.runProbes(msg.ShouldRunProbes)
|
||||||
fullStatus := s.statusRecorder.GetFullStatus()
|
fullStatus := s.statusRecorder.GetFullStatus()
|
||||||
pbFullStatus := toProtoFullStatus(fullStatus)
|
pbFullStatus := nbstatus.ToProtoFullStatus(fullStatus)
|
||||||
pbFullStatus.Events = s.statusRecorder.GetEventHistory()
|
pbFullStatus.Events = s.statusRecorder.GetEventHistory()
|
||||||
|
|
||||||
pbFullStatus.SshServerState = s.getSSHServerState()
|
pbFullStatus.SshServerState = s.getSSHServerState()
|
||||||
@@ -1240,7 +1232,7 @@ func (s *Server) RequestJWTAuth(
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDesktop := isUnixRunningDesktop()
|
isDesktop := isUnixRunningDesktop()
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isDesktop, false, hint)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isDesktop, hint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gstatus.Errorf(codes.Internal, "failed to create OAuth flow: %v", err)
|
return nil, gstatus.Errorf(codes.Internal, "failed to create OAuth flow: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1543,7 +1535,7 @@ func (s *Server) connect(ctx context.Context, config *profilemanager.Config, sta
|
|||||||
log.Tracef("running client connection")
|
log.Tracef("running client connection")
|
||||||
s.connectClient = internal.NewConnectClient(ctx, config, statusRecorder)
|
s.connectClient = internal.NewConnectClient(ctx, config, statusRecorder)
|
||||||
s.connectClient.SetSyncResponsePersistence(s.persistSyncResponse)
|
s.connectClient.SetSyncResponsePersistence(s.persistSyncResponse)
|
||||||
if err := s.connectClient.Run(runningChan); err != nil {
|
if err := s.connectClient.Run(runningChan, s.logFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -1617,94 +1609,6 @@ func parseEnvDuration(envVar string, defaultDuration time.Duration) time.Duratio
|
|||||||
return defaultDuration
|
return defaultDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
|
||||||
pbFullStatus := proto.FullStatus{
|
|
||||||
ManagementState: &proto.ManagementState{},
|
|
||||||
SignalState: &proto.SignalState{},
|
|
||||||
LocalPeerState: &proto.LocalPeerState{},
|
|
||||||
Peers: []*proto.PeerState{},
|
|
||||||
}
|
|
||||||
|
|
||||||
pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL
|
|
||||||
pbFullStatus.ManagementState.Connected = fullStatus.ManagementState.Connected
|
|
||||||
if err := fullStatus.ManagementState.Error; err != nil {
|
|
||||||
pbFullStatus.ManagementState.Error = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
pbFullStatus.SignalState.URL = fullStatus.SignalState.URL
|
|
||||||
pbFullStatus.SignalState.Connected = fullStatus.SignalState.Connected
|
|
||||||
if err := fullStatus.SignalState.Error; err != nil {
|
|
||||||
pbFullStatus.SignalState.Error = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
pbFullStatus.LocalPeerState.IP = fullStatus.LocalPeerState.IP
|
|
||||||
pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey
|
|
||||||
pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface
|
|
||||||
pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN
|
|
||||||
pbFullStatus.LocalPeerState.RosenpassPermissive = fullStatus.RosenpassState.Permissive
|
|
||||||
pbFullStatus.LocalPeerState.RosenpassEnabled = fullStatus.RosenpassState.Enabled
|
|
||||||
pbFullStatus.LocalPeerState.Networks = maps.Keys(fullStatus.LocalPeerState.Routes)
|
|
||||||
pbFullStatus.NumberOfForwardingRules = int32(fullStatus.NumOfForwardingRules)
|
|
||||||
pbFullStatus.LazyConnectionEnabled = fullStatus.LazyConnectionEnabled
|
|
||||||
|
|
||||||
for _, peerState := range fullStatus.Peers {
|
|
||||||
pbPeerState := &proto.PeerState{
|
|
||||||
IP: peerState.IP,
|
|
||||||
PubKey: peerState.PubKey,
|
|
||||||
ConnStatus: peerState.ConnStatus.String(),
|
|
||||||
ConnStatusUpdate: timestamppb.New(peerState.ConnStatusUpdate),
|
|
||||||
Relayed: peerState.Relayed,
|
|
||||||
LocalIceCandidateType: peerState.LocalIceCandidateType,
|
|
||||||
RemoteIceCandidateType: peerState.RemoteIceCandidateType,
|
|
||||||
LocalIceCandidateEndpoint: peerState.LocalIceCandidateEndpoint,
|
|
||||||
RemoteIceCandidateEndpoint: peerState.RemoteIceCandidateEndpoint,
|
|
||||||
RelayAddress: peerState.RelayServerAddress,
|
|
||||||
Fqdn: peerState.FQDN,
|
|
||||||
LastWireguardHandshake: timestamppb.New(peerState.LastWireguardHandshake),
|
|
||||||
BytesRx: peerState.BytesRx,
|
|
||||||
BytesTx: peerState.BytesTx,
|
|
||||||
RosenpassEnabled: peerState.RosenpassEnabled,
|
|
||||||
Networks: maps.Keys(peerState.GetRoutes()),
|
|
||||||
Latency: durationpb.New(peerState.Latency),
|
|
||||||
SshHostKey: peerState.SSHHostKey,
|
|
||||||
}
|
|
||||||
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, relayState := range fullStatus.Relays {
|
|
||||||
pbRelayState := &proto.RelayState{
|
|
||||||
URI: relayState.URI,
|
|
||||||
Available: relayState.Err == nil,
|
|
||||||
}
|
|
||||||
if err := relayState.Err; err != nil {
|
|
||||||
pbRelayState.Error = err.Error()
|
|
||||||
}
|
|
||||||
pbFullStatus.Relays = append(pbFullStatus.Relays, pbRelayState)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dnsState := range fullStatus.NSGroupStates {
|
|
||||||
var err string
|
|
||||||
if dnsState.Error != nil {
|
|
||||||
err = dnsState.Error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
var servers []string
|
|
||||||
for _, server := range dnsState.Servers {
|
|
||||||
servers = append(servers, server.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
pbDnsState := &proto.NSGroupState{
|
|
||||||
Servers: servers,
|
|
||||||
Domains: dnsState.Domains,
|
|
||||||
Enabled: dnsState.Enabled,
|
|
||||||
Error: err,
|
|
||||||
}
|
|
||||||
pbFullStatus.DnsServers = append(pbFullStatus.DnsServers, pbDnsState)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pbFullStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendTerminalNotification sends a terminal notification message
|
// sendTerminalNotification sends a terminal notification message
|
||||||
// to inform the user that the NetBird connection session has expired.
|
// to inform the user that the NetBird connection session has expired.
|
||||||
func sendTerminalNotification() error {
|
func sendTerminalNotification() error {
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers"
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
|
|
||||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||||
"github.com/netbirdio/netbird/management/server/groups"
|
"github.com/netbirdio/netbird/management/server/groups"
|
||||||
|
"github.com/netbirdio/netbird/management/server/peers/ephemeral/manager"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
|
"github.com/netbirdio/netbird/management/server/peers"
|
||||||
"github.com/netbirdio/netbird/management/server/permissions"
|
"github.com/netbirdio/netbird/management/server/permissions"
|
||||||
"github.com/netbirdio/netbird/management/server/settings"
|
"github.com/netbirdio/netbird/management/server/settings"
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
@@ -294,6 +295,7 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
}
|
}
|
||||||
t.Cleanup(cleanUp)
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
|
jobManager := job.NewJobManager(nil, store)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -316,17 +318,14 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
|
|
||||||
requestBuffer := server.NewAccountRequestBuffer(context.Background(), store)
|
requestBuffer := server.NewAccountRequestBuffer(context.Background(), store)
|
||||||
peersUpdateManager := update_channel.NewPeersUpdateManager(metrics)
|
peersUpdateManager := update_channel.NewPeersUpdateManager(metrics)
|
||||||
networkMapController := controller.NewController(context.Background(), store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), manager.NewEphemeralManager(store, peersManager), config)
|
networkMapController := controller.NewController(context.Background(), store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock())
|
||||||
accountManager, err := server.BuildManager(context.Background(), config, store, networkMapController, nil, "", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
accountManager, err := server.BuildManager(context.Background(), config, store, networkMapController, jobManager, nil, "", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsManager, err := nbgrpc.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
secretsManager := nbgrpc.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
||||||
if err != nil {
|
mgmtServer, err := nbgrpc.NewServer(config, accountManager, settingsMockManager, peersUpdateManager, jobManager, secretsManager, nil, &manager.EphemeralManager{}, nil, &server.MockIntegratedValidator{}, networkMapController)
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
mgmtServer, err := nbgrpc.NewServer(config, accountManager, settingsMockManager, secretsManager, nil, nil, &server.MockIntegratedValidator{}, networkMapController)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
"github.com/netbirdio/netbird/client/ssh/detection"
|
"github.com/netbirdio/netbird/client/ssh/detection"
|
||||||
"github.com/netbirdio/netbird/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -279,7 +278,6 @@ type DialOptions struct {
|
|||||||
DaemonAddr string
|
DaemonAddr string
|
||||||
SkipCachedToken bool
|
SkipCachedToken bool
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
NoBrowser bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connects to the given ssh server with specified options
|
// Dial connects to the given ssh server with specified options
|
||||||
@@ -309,7 +307,7 @@ func Dial(ctx context.Context, addr, user string, opts DialOptions) (*Client, er
|
|||||||
config.Auth = append(config.Auth, authMethod)
|
config.Auth = append(config.Auth, authMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialWithJWT(ctx, "tcp", addr, config, daemonAddr, opts.SkipCachedToken, opts.NoBrowser)
|
return dialWithJWT(ctx, "tcp", addr, config, daemonAddr, opts.SkipCachedToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialSSH establishes an SSH connection without JWT authentication
|
// dialSSH establishes an SSH connection without JWT authentication
|
||||||
@@ -335,7 +333,7 @@ func dialSSH(ctx context.Context, network, addr string, config *ssh.ClientConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dialWithJWT establishes an SSH connection with optional JWT authentication based on server detection
|
// dialWithJWT establishes an SSH connection with optional JWT authentication based on server detection
|
||||||
func dialWithJWT(ctx context.Context, network, addr string, config *ssh.ClientConfig, daemonAddr string, skipCache, noBrowser bool) (*Client, error) {
|
func dialWithJWT(ctx context.Context, network, addr string, config *ssh.ClientConfig, daemonAddr string, skipCache bool) (*Client, error) {
|
||||||
host, portStr, err := net.SplitHostPort(addr)
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parse address %s: %w", addr, err)
|
return nil, fmt.Errorf("parse address %s: %w", addr, err)
|
||||||
@@ -345,13 +343,10 @@ func dialWithJWT(ctx context.Context, network, addr string, config *ssh.ClientCo
|
|||||||
return nil, fmt.Errorf("parse port %s: %w", portStr, err)
|
return nil, fmt.Errorf("parse port %s: %w", portStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
detectionCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
dialer := &net.Dialer{Timeout: detection.Timeout}
|
||||||
defer cancel()
|
serverType, err := detection.DetectSSHServerType(ctx, dialer, host, port)
|
||||||
|
|
||||||
dialer := &net.Dialer{}
|
|
||||||
serverType, err := detection.DetectSSHServerType(detectionCtx, dialer, host, port)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SSH server detection: %w", err)
|
return nil, fmt.Errorf("SSH server detection failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !serverType.RequiresJWT() {
|
if !serverType.RequiresJWT() {
|
||||||
@@ -361,7 +356,7 @@ func dialWithJWT(ctx context.Context, network, addr string, config *ssh.ClientCo
|
|||||||
jwtCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
jwtCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
jwtToken, err := requestJWTToken(jwtCtx, daemonAddr, skipCache, noBrowser)
|
jwtToken, err := requestJWTToken(jwtCtx, daemonAddr, skipCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request JWT token: %w", err)
|
return nil, fmt.Errorf("request JWT token: %w", err)
|
||||||
}
|
}
|
||||||
@@ -371,7 +366,7 @@ func dialWithJWT(ctx context.Context, network, addr string, config *ssh.ClientCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// requestJWTToken requests a JWT token from the NetBird daemon
|
// requestJWTToken requests a JWT token from the NetBird daemon
|
||||||
func requestJWTToken(ctx context.Context, daemonAddr string, skipCache, noBrowser bool) (string, error) {
|
func requestJWTToken(ctx context.Context, daemonAddr string, skipCache bool) (string, error) {
|
||||||
hint := profilemanager.GetLoginHint()
|
hint := profilemanager.GetLoginHint()
|
||||||
|
|
||||||
conn, err := connectToDaemon(daemonAddr)
|
conn, err := connectToDaemon(daemonAddr)
|
||||||
@@ -381,13 +376,7 @@ func requestJWTToken(ctx context.Context, daemonAddr string, skipCache, noBrowse
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
client := proto.NewDaemonServiceClient(conn)
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
return nbssh.RequestJWTToken(ctx, client, os.Stdout, os.Stderr, !skipCache, hint)
|
||||||
var browserOpener func(string) error
|
|
||||||
if !noBrowser {
|
|
||||||
browserOpener = util.OpenBrowser
|
|
||||||
}
|
|
||||||
|
|
||||||
return nbssh.RequestJWTToken(ctx, client, os.Stdout, os.Stderr, !skipCache, hint, browserOpener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyHostKeyViaDaemon verifies SSH host key by querying the NetBird daemon
|
// verifyHostKeyViaDaemon verifies SSH host key by querying the NetBird daemon
|
||||||
|
|||||||
@@ -67,31 +67,8 @@ func (d *DaemonHostKeyVerifier) VerifySSHHostKey(peerAddress string, presentedKe
|
|||||||
return VerifyHostKey(storedKeyData, presentedKey, peerAddress)
|
return VerifyHostKey(storedKeyData, presentedKey, peerAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// printAuthInstructions prints authentication instructions to stderr
|
|
||||||
func printAuthInstructions(stderr io.Writer, authResponse *proto.RequestJWTAuthResponse, browserWillOpen bool) {
|
|
||||||
_, _ = fmt.Fprintln(stderr, "SSH authentication required.")
|
|
||||||
|
|
||||||
if browserWillOpen {
|
|
||||||
_, _ = fmt.Fprintln(stderr, "Please do the SSO login in your browser.")
|
|
||||||
_, _ = fmt.Fprintln(stderr, "If your browser didn't open automatically, use this URL to log in:")
|
|
||||||
_, _ = fmt.Fprintln(stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(stderr, "%s\n", authResponse.VerificationURIComplete)
|
|
||||||
|
|
||||||
if authResponse.UserCode != "" {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Or visit: %s and enter code: %s\n", authResponse.VerificationURI, authResponse.UserCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if browserWillOpen {
|
|
||||||
_, _ = fmt.Fprintln(stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(stderr, "Waiting for authentication...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestJWTToken requests or retrieves a JWT token for SSH authentication
|
// RequestJWTToken requests or retrieves a JWT token for SSH authentication
|
||||||
func RequestJWTToken(ctx context.Context, client proto.DaemonServiceClient, stdout, stderr io.Writer, useCache bool, hint string, openBrowser func(string) error) (string, error) {
|
func RequestJWTToken(ctx context.Context, client proto.DaemonServiceClient, stdout, stderr io.Writer, useCache bool, hint string) (string, error) {
|
||||||
req := &proto.RequestJWTAuthRequest{}
|
req := &proto.RequestJWTAuthRequest{}
|
||||||
if hint != "" {
|
if hint != "" {
|
||||||
req.Hint = &hint
|
req.Hint = &hint
|
||||||
@@ -107,13 +84,12 @@ func RequestJWTToken(ctx context.Context, client proto.DaemonServiceClient, stdo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if stderr != nil {
|
if stderr != nil {
|
||||||
printAuthInstructions(stderr, authResponse, openBrowser != nil)
|
_, _ = fmt.Fprintln(stderr, "SSH authentication required.")
|
||||||
}
|
_, _ = fmt.Fprintf(stderr, "Please visit: %s\n", authResponse.VerificationURIComplete)
|
||||||
|
if authResponse.UserCode != "" {
|
||||||
if openBrowser != nil {
|
_, _ = fmt.Fprintf(stderr, "Or visit: %s and enter code: %s\n", authResponse.VerificationURI, authResponse.UserCode)
|
||||||
if err := openBrowser(authResponse.VerificationURIComplete); err != nil {
|
|
||||||
log.Debugf("open browser: %v", err)
|
|
||||||
}
|
}
|
||||||
|
_, _ = fmt.Fprintln(stderr, "Waiting for authentication...")
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenResponse, err := client.WaitJWTToken(ctx, &proto.WaitJWTTokenRequest{
|
tokenResponse, err := client.WaitJWTToken(ctx, &proto.WaitJWTTokenRequest{
|
||||||
|
|||||||
@@ -189,7 +189,12 @@ func (m *Manager) buildPeerConfig(allHostPatterns []string) (string, error) {
|
|||||||
|
|
||||||
hostLine := strings.Join(deduplicatedPatterns, " ")
|
hostLine := strings.Join(deduplicatedPatterns, " ")
|
||||||
config := fmt.Sprintf("Host %s\n", hostLine)
|
config := fmt.Sprintf("Host %s\n", hostLine)
|
||||||
config += fmt.Sprintf(" Match exec \"%s ssh detect %%h %%p\"\n", execPath)
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
config += fmt.Sprintf(" Match exec \"%s ssh detect %%h %%p\"\n", execPath)
|
||||||
|
} else {
|
||||||
|
config += fmt.Sprintf(" Match exec \"%s ssh detect %%h %%p 2>/dev/null\"\n", execPath)
|
||||||
|
}
|
||||||
config += " PreferredAuthentications password,publickey,keyboard-interactive\n"
|
config += " PreferredAuthentications password,publickey,keyboard-interactive\n"
|
||||||
config += " PasswordAuthentication yes\n"
|
config += " PasswordAuthentication yes\n"
|
||||||
config += " PubkeyAuthentication yes\n"
|
config += " PubkeyAuthentication yes\n"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package detection
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,8 +19,8 @@ const (
|
|||||||
// JWTRequiredMarker is appended to responses when JWT is required
|
// JWTRequiredMarker is appended to responses when JWT is required
|
||||||
JWTRequiredMarker = "NetBird-JWT-Required"
|
JWTRequiredMarker = "NetBird-JWT-Required"
|
||||||
|
|
||||||
// DefaultTimeout is the default timeout for SSH server detection
|
// Timeout is the timeout for SSH server detection
|
||||||
DefaultTimeout = 5 * time.Second
|
Timeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerType string
|
type ServerType string
|
||||||
@@ -62,20 +61,21 @@ func DetectSSHServerType(ctx context.Context, dialer Dialer, host string, port i
|
|||||||
|
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", targetAddr)
|
conn, err := dialer.DialContext(ctx, "tcp", targetAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServerTypeRegular, fmt.Errorf("connect to %s: %w", targetAddr, err)
|
log.Debugf("SSH connection failed for detection: %v", err)
|
||||||
|
return ServerTypeRegular, nil
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
if deadline, ok := ctx.Deadline(); ok {
|
if err := conn.SetReadDeadline(time.Now().Add(Timeout)); err != nil {
|
||||||
if err := conn.SetReadDeadline(deadline); err != nil {
|
log.Debugf("set read deadline: %v", err)
|
||||||
return ServerTypeRegular, fmt.Errorf("set read deadline: %w", err)
|
return ServerTypeRegular, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(conn)
|
reader := bufio.NewReader(conn)
|
||||||
serverBanner, err := reader.ReadString('\n')
|
serverBanner, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServerTypeRegular, fmt.Errorf("read SSH banner: %w", err)
|
log.Debugf("read SSH banner: %v", err)
|
||||||
|
return ServerTypeRegular, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
serverBanner = strings.TrimSpace(serverBanner)
|
serverBanner = strings.TrimSpace(serverBanner)
|
||||||
|
|||||||
@@ -35,16 +35,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SSHProxy struct {
|
type SSHProxy struct {
|
||||||
daemonAddr string
|
daemonAddr string
|
||||||
targetHost string
|
targetHost string
|
||||||
targetPort int
|
targetPort int
|
||||||
stderr io.Writer
|
stderr io.Writer
|
||||||
conn *grpc.ClientConn
|
conn *grpc.ClientConn
|
||||||
daemonClient proto.DaemonServiceClient
|
daemonClient proto.DaemonServiceClient
|
||||||
browserOpener func(string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(daemonAddr, targetHost string, targetPort int, stderr io.Writer, browserOpener func(string) error) (*SSHProxy, error) {
|
func New(daemonAddr, targetHost string, targetPort int, stderr io.Writer) (*SSHProxy, error) {
|
||||||
grpcAddr := strings.TrimPrefix(daemonAddr, "tcp://")
|
grpcAddr := strings.TrimPrefix(daemonAddr, "tcp://")
|
||||||
grpcConn, err := grpc.NewClient(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
grpcConn, err := grpc.NewClient(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -52,13 +51,12 @@ func New(daemonAddr, targetHost string, targetPort int, stderr io.Writer, browse
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &SSHProxy{
|
return &SSHProxy{
|
||||||
daemonAddr: daemonAddr,
|
daemonAddr: daemonAddr,
|
||||||
targetHost: targetHost,
|
targetHost: targetHost,
|
||||||
targetPort: targetPort,
|
targetPort: targetPort,
|
||||||
stderr: stderr,
|
stderr: stderr,
|
||||||
conn: grpcConn,
|
conn: grpcConn,
|
||||||
daemonClient: proto.NewDaemonServiceClient(grpcConn),
|
daemonClient: proto.NewDaemonServiceClient(grpcConn),
|
||||||
browserOpener: browserOpener,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +70,7 @@ func (p *SSHProxy) Close() error {
|
|||||||
func (p *SSHProxy) Connect(ctx context.Context) error {
|
func (p *SSHProxy) Connect(ctx context.Context) error {
|
||||||
hint := profilemanager.GetLoginHint()
|
hint := profilemanager.GetLoginHint()
|
||||||
|
|
||||||
jwtToken, err := nbssh.RequestJWTToken(ctx, p.daemonClient, nil, p.stderr, true, hint, p.browserOpener)
|
jwtToken, err := nbssh.RequestJWTToken(ctx, p.daemonClient, nil, p.stderr, true, hint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(jwtAuthErrorMsg, err)
|
return fmt.Errorf(jwtAuthErrorMsg, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func TestSSHProxy_Connect(t *testing.T) {
|
|||||||
validToken := generateValidJWT(t, privateKey, issuer, audience)
|
validToken := generateValidJWT(t, privateKey, issuer, audience)
|
||||||
mockDaemon.setJWTToken(validToken)
|
mockDaemon.setJWTToken(validToken)
|
||||||
|
|
||||||
proxyInstance, err := New(mockDaemon.addr, host, port, nil, nil)
|
proxyInstance, err := New(mockDaemon.addr, host, port, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
clientConn, proxyConn := net.Pipe()
|
clientConn, proxyConn := net.Pipe()
|
||||||
|
|||||||
@@ -42,11 +42,6 @@ func (s *Server) detectSuPtySupport(context.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectUtilLinuxLogin always returns false on JS/WASM
|
|
||||||
func (s *Server) detectUtilLinuxLogin(context.Context) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeCommandWithPty is not supported on JS/WASM
|
// executeCommandWithPty is not supported on JS/WASM
|
||||||
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
||||||
logger.Errorf("PTY command execution not supported on JS/WASM")
|
logger.Errorf("PTY command execution not supported on JS/WASM")
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -76,29 +75,6 @@ func (s *Server) detectSuPtySupport(ctx context.Context) bool {
|
|||||||
return supported
|
return supported
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectUtilLinuxLogin checks if login is from util-linux (vs shadow-utils).
|
|
||||||
// util-linux login uses vhangup() which requires setsid wrapper to avoid killing parent.
|
|
||||||
// See https://bugs.debian.org/1078023 for details.
|
|
||||||
func (s *Server) detectUtilLinuxLogin(ctx context.Context) bool {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "login", "--version")
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("login --version failed (likely shadow-utils): %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
isUtilLinux := strings.Contains(string(output), "util-linux")
|
|
||||||
log.Debugf("util-linux login detected: %v", isUtilLinux)
|
|
||||||
return isUtilLinux
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSuCommand creates a command using su -l -c for privilege switching
|
// createSuCommand creates a command using su -l -c for privilege switching
|
||||||
func (s *Server) createSuCommand(session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
|
func (s *Server) createSuCommand(session ssh.Session, localUser *user.User, hasPty bool) (*exec.Cmd, error) {
|
||||||
suPath, err := exec.LookPath("su")
|
suPath, err := exec.LookPath("su")
|
||||||
@@ -168,7 +144,7 @@ func (s *Server) handlePty(logger *log.Entry, session ssh.Session, privilegeResu
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("starting interactive shell: %s", strings.Join(execCmd.Args, " "))
|
logger.Infof("starting interactive shell: %s", execCmd.Path)
|
||||||
return s.runPtyCommand(logger, session, execCmd, ptyReq, winCh)
|
return s.runPtyCommand(logger, session, execCmd, ptyReq, winCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,11 +383,6 @@ func (s *Server) detectSuPtySupport(context.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectUtilLinuxLogin always returns false on Windows
|
|
||||||
func (s *Server) detectUtilLinuxLogin(context.Context) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeCommandWithPty executes a command with PTY allocation on Windows using ConPty
|
// executeCommandWithPty executes a command with PTY allocation on Windows using ConPty
|
||||||
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
func (s *Server) executeCommandWithPty(logger *log.Entry, session ssh.Session, execCmd *exec.Cmd, privilegeResult PrivilegeCheckResult, ptyReq ssh.Pty, winCh <-chan ssh.Window) bool {
|
||||||
command := session.RawCommand()
|
command := session.RawCommand()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func TestJWTEnforcement(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dialer := &net.Dialer{}
|
dialer := &net.Dialer{Timeout: detection.Timeout}
|
||||||
serverType, err := detection.DetectSSHServerType(context.Background(), dialer, host, port)
|
serverType, err := detection.DetectSSHServerType(context.Background(), dialer, host, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Detection failed: %v", err)
|
t.Logf("Detection failed: %v", err)
|
||||||
@@ -93,7 +93,7 @@ func TestJWTEnforcement(t *testing.T) {
|
|||||||
portNoJWT, err := strconv.Atoi(portStrNoJWT)
|
portNoJWT, err := strconv.Atoi(portStrNoJWT)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
dialer := &net.Dialer{}
|
dialer := &net.Dialer{Timeout: detection.Timeout}
|
||||||
serverType, err := detection.DetectSSHServerType(context.Background(), dialer, hostNoJWT, portNoJWT)
|
serverType, err := detection.DetectSSHServerType(context.Background(), dialer, hostNoJWT, portNoJWT)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, detection.ServerTypeNetBirdNoJWT, serverType)
|
assert.Equal(t, detection.ServerTypeNetBirdNoJWT, serverType)
|
||||||
@@ -218,7 +218,7 @@ func TestJWTDetection(t *testing.T) {
|
|||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
dialer := &net.Dialer{}
|
dialer := &net.Dialer{Timeout: detection.Timeout}
|
||||||
serverType, err := detection.DetectSSHServerType(context.Background(), dialer, host, port)
|
serverType, err := detection.DetectSSHServerType(context.Background(), dialer, host, port)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, detection.ServerTypeNetBirdJWT, serverType)
|
assert.Equal(t, detection.ServerTypeNetBirdJWT, serverType)
|
||||||
|
|||||||
@@ -138,8 +138,7 @@ type Server struct {
|
|||||||
jwtExtractor *jwt.ClaimsExtractor
|
jwtExtractor *jwt.ClaimsExtractor
|
||||||
jwtConfig *JWTConfig
|
jwtConfig *JWTConfig
|
||||||
|
|
||||||
suSupportsPty bool
|
suSupportsPty bool
|
||||||
loginIsUtilLinux bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type JWTConfig struct {
|
type JWTConfig struct {
|
||||||
@@ -194,7 +193,6 @@ func (s *Server) Start(ctx context.Context, addr netip.AddrPort) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.suSupportsPty = s.detectSuPtySupport(ctx)
|
s.suSupportsPty = s.detectSuPtySupport(ctx)
|
||||||
s.loginIsUtilLinux = s.detectUtilLinuxLogin(ctx)
|
|
||||||
|
|
||||||
ln, addrDesc, err := s.createListener(ctx, addr)
|
ln, addrDesc, err := s.createListener(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -87,8 +87,11 @@ func (s *Server) getLoginCmd(username string, remoteAddr net.Addr) (string, []st
|
|||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
p, a := s.getLinuxLoginCmd(loginPath, username, addrPort.Addr().String())
|
// Special handling for Arch Linux without /etc/pam.d/remote
|
||||||
return p, a, nil
|
if s.fileExists("/etc/arch-release") && !s.fileExists("/etc/pam.d/remote") {
|
||||||
|
return loginPath, []string{"-f", username, "-p"}, nil
|
||||||
|
}
|
||||||
|
return loginPath, []string{"-f", username, "-h", addrPort.Addr().String(), "-p"}, nil
|
||||||
case "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
|
case "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
|
||||||
return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), username}, nil
|
return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), username}, nil
|
||||||
default:
|
default:
|
||||||
@@ -96,37 +99,7 @@ func (s *Server) getLoginCmd(username string, remoteAddr net.Addr) (string, []st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLinuxLoginCmd returns the login command for Linux systems.
|
// fileExists checks if a file exists (helper for login command logic)
|
||||||
// Handles differences between util-linux and shadow-utils login implementations.
|
|
||||||
func (s *Server) getLinuxLoginCmd(loginPath, username, remoteIP string) (string, []string) {
|
|
||||||
// Special handling for Arch Linux without /etc/pam.d/remote
|
|
||||||
var loginArgs []string
|
|
||||||
if s.fileExists("/etc/arch-release") && !s.fileExists("/etc/pam.d/remote") {
|
|
||||||
loginArgs = []string{"-f", username, "-p"}
|
|
||||||
} else {
|
|
||||||
loginArgs = []string{"-f", username, "-h", remoteIP, "-p"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// util-linux login requires setsid -c to create a new session and set the
|
|
||||||
// controlling terminal. Without this, vhangup() kills the parent process.
|
|
||||||
// See https://bugs.debian.org/1078023 for details.
|
|
||||||
// TODO: handle this via the executor using syscall.Setsid() + TIOCSCTTY + syscall.Exec()
|
|
||||||
// to avoid external setsid dependency.
|
|
||||||
if !s.loginIsUtilLinux {
|
|
||||||
return loginPath, loginArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
setsidPath, err := exec.LookPath("setsid")
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("setsid not available but util-linux login detected, login may fail: %v", err)
|
|
||||||
return loginPath, loginArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
args := append([]string{"-w", "-c", loginPath}, loginArgs...)
|
|
||||||
return setsidPath, args
|
|
||||||
}
|
|
||||||
|
|
||||||
// fileExists checks if a file exists
|
|
||||||
func (s *Server) fileExists(path string) bool {
|
func (s *Server) fileExists(path string) bool {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ func IsSystemAccount(username string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
return strings.HasSuffix(username, "$")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterTestUserCleanup registers a test user for cleanup
|
// RegisterTestUserCleanup registers a test user for cleanup
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/user"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestUserCurrentBehavior validates user.Current() behavior on Windows.
|
|
||||||
// When running as SYSTEM on a domain-joined machine, user.Current() returns:
|
|
||||||
// - Username: Computer account name (e.g., "DOMAIN\MACHINE$")
|
|
||||||
// - SID: SYSTEM SID (S-1-5-18)
|
|
||||||
func TestUserCurrentBehavior(t *testing.T) {
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
t.Skip("Windows-specific test")
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUser, err := user.Current()
|
|
||||||
require.NoError(t, err, "Should be able to get current user")
|
|
||||||
|
|
||||||
t.Logf("Current user - Username: %s, SID: %s", currentUser.Username, currentUser.Uid)
|
|
||||||
|
|
||||||
// When running as SYSTEM, validate expected behavior
|
|
||||||
if currentUser.Uid == "S-1-5-18" {
|
|
||||||
t.Run("SYSTEM_account_behavior", func(t *testing.T) {
|
|
||||||
// SID must be S-1-5-18 for SYSTEM
|
|
||||||
require.Equal(t, "S-1-5-18", currentUser.Uid,
|
|
||||||
"SYSTEM account must have SID S-1-5-18")
|
|
||||||
|
|
||||||
// Username can be either "NT AUTHORITY\SYSTEM" (standalone)
|
|
||||||
// or "DOMAIN\MACHINE$" (domain-joined)
|
|
||||||
username := currentUser.Username
|
|
||||||
isNTAuthority := strings.Contains(strings.ToUpper(username), "NT AUTHORITY")
|
|
||||||
isComputerAccount := strings.HasSuffix(username, "$")
|
|
||||||
|
|
||||||
assert.True(t, isNTAuthority || isComputerAccount,
|
|
||||||
"Username should be either 'NT AUTHORITY\\SYSTEM' or computer account (ending with $), got: %s",
|
|
||||||
username)
|
|
||||||
|
|
||||||
if isComputerAccount {
|
|
||||||
t.Logf("SYSTEM as computer account: %s", username)
|
|
||||||
} else if isNTAuthority {
|
|
||||||
t.Logf("SYSTEM as NT AUTHORITY\\SYSTEM")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that IsSystemAccount correctly identifies system accounts
|
|
||||||
t.Run("IsSystemAccount_validation", func(t *testing.T) {
|
|
||||||
// Test with current user if it's a system account
|
|
||||||
if currentUser.Uid == "S-1-5-18" || // SYSTEM
|
|
||||||
currentUser.Uid == "S-1-5-19" || // LOCAL SERVICE
|
|
||||||
currentUser.Uid == "S-1-5-20" { // NETWORK SERVICE
|
|
||||||
|
|
||||||
result := IsSystemAccount(currentUser.Username)
|
|
||||||
assert.True(t, result,
|
|
||||||
"IsSystemAccount should recognize system account: %s (SID: %s)",
|
|
||||||
currentUser.Username, currentUser.Uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test explicit cases
|
|
||||||
testCases := []struct {
|
|
||||||
username string
|
|
||||||
expected bool
|
|
||||||
reason string
|
|
||||||
}{
|
|
||||||
{"NT AUTHORITY\\SYSTEM", true, "NT AUTHORITY\\SYSTEM"},
|
|
||||||
{"system", true, "system"},
|
|
||||||
{"SYSTEM", true, "SYSTEM (case insensitive)"},
|
|
||||||
{"NT AUTHORITY\\LOCAL SERVICE", true, "LOCAL SERVICE"},
|
|
||||||
{"NT AUTHORITY\\NETWORK SERVICE", true, "NETWORK SERVICE"},
|
|
||||||
{"DOMAIN\\MACHINE$", true, "computer account (ends with $)"},
|
|
||||||
{"WORKGROUP\\WIN2K19-C2$", true, "computer account (ends with $)"},
|
|
||||||
{"Administrator", false, "Administrator is not a system account"},
|
|
||||||
{"alice", false, "regular user"},
|
|
||||||
{"DOMAIN\\alice", false, "domain user"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.username, func(t *testing.T) {
|
|
||||||
result := IsSystemAccount(tc.username)
|
|
||||||
assert.Equal(t, tc.expected, result,
|
|
||||||
"IsSystemAccount(%q) should be %v because: %s",
|
|
||||||
tc.username, tc.expected, tc.reason)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestComputerAccountDetection validates computer account detection.
|
|
||||||
func TestComputerAccountDetection(t *testing.T) {
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
t.Skip("Windows-specific test")
|
|
||||||
}
|
|
||||||
|
|
||||||
computerAccounts := []string{
|
|
||||||
"MACHINE$",
|
|
||||||
"WIN2K19-C2$",
|
|
||||||
"DOMAIN\\MACHINE$",
|
|
||||||
"WORKGROUP\\SERVER$",
|
|
||||||
"server.domain.com$",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range computerAccounts {
|
|
||||||
t.Run(account, func(t *testing.T) {
|
|
||||||
result := IsSystemAccount(account)
|
|
||||||
assert.True(t, result,
|
|
||||||
"Computer account %q should be recognized as system account", account)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/anonymize"
|
"github.com/netbirdio/netbird/client/anonymize"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
probeRelay "github.com/netbirdio/netbird/client/internal/relay"
|
probeRelay "github.com/netbirdio/netbird/client/internal/relay"
|
||||||
@@ -115,9 +119,7 @@ type OutputOverview struct {
|
|||||||
SSHServerState SSHServerStateOutput `json:"sshServer" yaml:"sshServer"`
|
SSHServerState SSHServerStateOutput `json:"sshServer" yaml:"sshServer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, connectionTypeFilter string, profName string) OutputOverview {
|
func ConvertToStatusOutputOverview(pbFullStatus *proto.FullStatus, anon bool, daemonVersion string, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, connectionTypeFilter string, profName string) OutputOverview {
|
||||||
pbFullStatus := resp.GetFullStatus()
|
|
||||||
|
|
||||||
managementState := pbFullStatus.GetManagementState()
|
managementState := pbFullStatus.GetManagementState()
|
||||||
managementOverview := ManagementStateOutput{
|
managementOverview := ManagementStateOutput{
|
||||||
URL: managementState.GetURL(),
|
URL: managementState.GetURL(),
|
||||||
@@ -133,13 +135,13 @@ func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, status
|
|||||||
}
|
}
|
||||||
|
|
||||||
relayOverview := mapRelays(pbFullStatus.GetRelays())
|
relayOverview := mapRelays(pbFullStatus.GetRelays())
|
||||||
peersOverview := mapPeers(resp.GetFullStatus().GetPeers(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter, connectionTypeFilter)
|
|
||||||
sshServerOverview := mapSSHServer(pbFullStatus.GetSshServerState())
|
sshServerOverview := mapSSHServer(pbFullStatus.GetSshServerState())
|
||||||
|
peersOverview := mapPeers(pbFullStatus.GetPeers(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter, connectionTypeFilter)
|
||||||
|
|
||||||
overview := OutputOverview{
|
overview := OutputOverview{
|
||||||
Peers: peersOverview,
|
Peers: peersOverview,
|
||||||
CliVersion: version.NetbirdVersion(),
|
CliVersion: version.NetbirdVersion(),
|
||||||
DaemonVersion: resp.GetDaemonVersion(),
|
DaemonVersion: daemonVersion,
|
||||||
ManagementState: managementOverview,
|
ManagementState: managementOverview,
|
||||||
SignalState: signalOverview,
|
SignalState: signalOverview,
|
||||||
Relays: relayOverview,
|
Relays: relayOverview,
|
||||||
@@ -544,6 +546,94 @@ func ParseToFullDetailSummary(overview OutputOverview) string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
||||||
|
pbFullStatus := proto.FullStatus{
|
||||||
|
ManagementState: &proto.ManagementState{},
|
||||||
|
SignalState: &proto.SignalState{},
|
||||||
|
LocalPeerState: &proto.LocalPeerState{},
|
||||||
|
Peers: []*proto.PeerState{},
|
||||||
|
}
|
||||||
|
|
||||||
|
pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL
|
||||||
|
pbFullStatus.ManagementState.Connected = fullStatus.ManagementState.Connected
|
||||||
|
if err := fullStatus.ManagementState.Error; err != nil {
|
||||||
|
pbFullStatus.ManagementState.Error = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
pbFullStatus.SignalState.URL = fullStatus.SignalState.URL
|
||||||
|
pbFullStatus.SignalState.Connected = fullStatus.SignalState.Connected
|
||||||
|
if err := fullStatus.SignalState.Error; err != nil {
|
||||||
|
pbFullStatus.SignalState.Error = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
pbFullStatus.LocalPeerState.IP = fullStatus.LocalPeerState.IP
|
||||||
|
pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey
|
||||||
|
pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface
|
||||||
|
pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN
|
||||||
|
pbFullStatus.LocalPeerState.RosenpassPermissive = fullStatus.RosenpassState.Permissive
|
||||||
|
pbFullStatus.LocalPeerState.RosenpassEnabled = fullStatus.RosenpassState.Enabled
|
||||||
|
pbFullStatus.LocalPeerState.Networks = maps.Keys(fullStatus.LocalPeerState.Routes)
|
||||||
|
pbFullStatus.NumberOfForwardingRules = int32(fullStatus.NumOfForwardingRules)
|
||||||
|
pbFullStatus.LazyConnectionEnabled = fullStatus.LazyConnectionEnabled
|
||||||
|
|
||||||
|
for _, peerState := range fullStatus.Peers {
|
||||||
|
pbPeerState := &proto.PeerState{
|
||||||
|
IP: peerState.IP,
|
||||||
|
PubKey: peerState.PubKey,
|
||||||
|
ConnStatus: peerState.ConnStatus.String(),
|
||||||
|
ConnStatusUpdate: timestamppb.New(peerState.ConnStatusUpdate),
|
||||||
|
Relayed: peerState.Relayed,
|
||||||
|
LocalIceCandidateType: peerState.LocalIceCandidateType,
|
||||||
|
RemoteIceCandidateType: peerState.RemoteIceCandidateType,
|
||||||
|
LocalIceCandidateEndpoint: peerState.LocalIceCandidateEndpoint,
|
||||||
|
RemoteIceCandidateEndpoint: peerState.RemoteIceCandidateEndpoint,
|
||||||
|
RelayAddress: peerState.RelayServerAddress,
|
||||||
|
Fqdn: peerState.FQDN,
|
||||||
|
LastWireguardHandshake: timestamppb.New(peerState.LastWireguardHandshake),
|
||||||
|
BytesRx: peerState.BytesRx,
|
||||||
|
BytesTx: peerState.BytesTx,
|
||||||
|
RosenpassEnabled: peerState.RosenpassEnabled,
|
||||||
|
Networks: maps.Keys(peerState.GetRoutes()),
|
||||||
|
Latency: durationpb.New(peerState.Latency),
|
||||||
|
SshHostKey: peerState.SSHHostKey,
|
||||||
|
}
|
||||||
|
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, relayState := range fullStatus.Relays {
|
||||||
|
pbRelayState := &proto.RelayState{
|
||||||
|
URI: relayState.URI,
|
||||||
|
Available: relayState.Err == nil,
|
||||||
|
}
|
||||||
|
if err := relayState.Err; err != nil {
|
||||||
|
pbRelayState.Error = err.Error()
|
||||||
|
}
|
||||||
|
pbFullStatus.Relays = append(pbFullStatus.Relays, pbRelayState)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dnsState := range fullStatus.NSGroupStates {
|
||||||
|
var err string
|
||||||
|
if dnsState.Error != nil {
|
||||||
|
err = dnsState.Error.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var servers []string
|
||||||
|
for _, server := range dnsState.Servers {
|
||||||
|
servers = append(servers, server.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
pbDnsState := &proto.NSGroupState{
|
||||||
|
Servers: servers,
|
||||||
|
Domains: dnsState.Domains,
|
||||||
|
Enabled: dnsState.Enabled,
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
pbFullStatus.DnsServers = append(pbFullStatus.DnsServers, pbDnsState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pbFullStatus
|
||||||
|
}
|
||||||
|
|
||||||
func parsePeers(peers PeersStateOutput, rosenpassEnabled, rosenpassPermissive bool) string {
|
func parsePeers(peers PeersStateOutput, rosenpassEnabled, rosenpassPermissive bool) string {
|
||||||
var (
|
var (
|
||||||
peersString = ""
|
peersString = ""
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ var overview = OutputOverview{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
|
func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
|
||||||
convertedResult := ConvertToStatusOutputOverview(resp, false, "", nil, nil, nil, "", "")
|
convertedResult := ConvertToStatusOutputOverview(resp.GetFullStatus(), false, resp.GetDaemonVersion(), "", nil, nil, nil, "", "")
|
||||||
|
|
||||||
assert.Equal(t, overview, convertedResult)
|
assert.Equal(t, overview, convertedResult)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,26 +120,6 @@ func (i *Info) SetFlags(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Info) CopyFlagsFrom(other *Info) {
|
|
||||||
i.SetFlags(
|
|
||||||
other.RosenpassEnabled,
|
|
||||||
other.RosenpassPermissive,
|
|
||||||
&other.ServerSSHAllowed,
|
|
||||||
other.DisableClientRoutes,
|
|
||||||
other.DisableServerRoutes,
|
|
||||||
other.DisableDNS,
|
|
||||||
other.DisableFirewall,
|
|
||||||
other.BlockLANAccess,
|
|
||||||
other.BlockInbound,
|
|
||||||
other.LazyConnectionEnabled,
|
|
||||||
&other.EnableSSHRoot,
|
|
||||||
&other.EnableSSHSFTP,
|
|
||||||
&other.EnableSSHLocalPortForwarding,
|
|
||||||
&other.EnableSSHRemotePortForwarding,
|
|
||||||
&other.DisableSSHAuth,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
||||||
func extractUserAgent(ctx context.Context) string {
|
func extractUserAgent(ctx context.Context) string {
|
||||||
md, hasMeta := metadata.FromOutgoingContext(ctx)
|
md, hasMeta := metadata.FromOutgoingContext(ctx)
|
||||||
|
|||||||
@@ -8,90 +8,6 @@ import (
|
|||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInfo_CopyFlagsFrom(t *testing.T) {
|
|
||||||
origin := &Info{}
|
|
||||||
serverSSHAllowed := true
|
|
||||||
enableSSHRoot := true
|
|
||||||
enableSSHSFTP := false
|
|
||||||
enableSSHLocalPortForwarding := true
|
|
||||||
enableSSHRemotePortForwarding := false
|
|
||||||
disableSSHAuth := true
|
|
||||||
origin.SetFlags(
|
|
||||||
true, // RosenpassEnabled
|
|
||||||
false, // RosenpassPermissive
|
|
||||||
&serverSSHAllowed,
|
|
||||||
true, // DisableClientRoutes
|
|
||||||
false, // DisableServerRoutes
|
|
||||||
true, // DisableDNS
|
|
||||||
false, // DisableFirewall
|
|
||||||
true, // BlockLANAccess
|
|
||||||
false, // BlockInbound
|
|
||||||
true, // LazyConnectionEnabled
|
|
||||||
&enableSSHRoot,
|
|
||||||
&enableSSHSFTP,
|
|
||||||
&enableSSHLocalPortForwarding,
|
|
||||||
&enableSSHRemotePortForwarding,
|
|
||||||
&disableSSHAuth,
|
|
||||||
)
|
|
||||||
|
|
||||||
got := &Info{}
|
|
||||||
got.CopyFlagsFrom(origin)
|
|
||||||
|
|
||||||
if got.RosenpassEnabled != true {
|
|
||||||
t.Fatalf("RosenpassEnabled not copied: got %v", got.RosenpassEnabled)
|
|
||||||
}
|
|
||||||
if got.RosenpassPermissive != false {
|
|
||||||
t.Fatalf("RosenpassPermissive not copied: got %v", got.RosenpassPermissive)
|
|
||||||
}
|
|
||||||
if got.ServerSSHAllowed != true {
|
|
||||||
t.Fatalf("ServerSSHAllowed not copied: got %v", got.ServerSSHAllowed)
|
|
||||||
}
|
|
||||||
if got.DisableClientRoutes != true {
|
|
||||||
t.Fatalf("DisableClientRoutes not copied: got %v", got.DisableClientRoutes)
|
|
||||||
}
|
|
||||||
if got.DisableServerRoutes != false {
|
|
||||||
t.Fatalf("DisableServerRoutes not copied: got %v", got.DisableServerRoutes)
|
|
||||||
}
|
|
||||||
if got.DisableDNS != true {
|
|
||||||
t.Fatalf("DisableDNS not copied: got %v", got.DisableDNS)
|
|
||||||
}
|
|
||||||
if got.DisableFirewall != false {
|
|
||||||
t.Fatalf("DisableFirewall not copied: got %v", got.DisableFirewall)
|
|
||||||
}
|
|
||||||
if got.BlockLANAccess != true {
|
|
||||||
t.Fatalf("BlockLANAccess not copied: got %v", got.BlockLANAccess)
|
|
||||||
}
|
|
||||||
if got.BlockInbound != false {
|
|
||||||
t.Fatalf("BlockInbound not copied: got %v", got.BlockInbound)
|
|
||||||
}
|
|
||||||
if got.LazyConnectionEnabled != true {
|
|
||||||
t.Fatalf("LazyConnectionEnabled not copied: got %v", got.LazyConnectionEnabled)
|
|
||||||
}
|
|
||||||
if got.EnableSSHRoot != true {
|
|
||||||
t.Fatalf("EnableSSHRoot not copied: got %v", got.EnableSSHRoot)
|
|
||||||
}
|
|
||||||
if got.EnableSSHSFTP != false {
|
|
||||||
t.Fatalf("EnableSSHSFTP not copied: got %v", got.EnableSSHSFTP)
|
|
||||||
}
|
|
||||||
if got.EnableSSHLocalPortForwarding != true {
|
|
||||||
t.Fatalf("EnableSSHLocalPortForwarding not copied: got %v", got.EnableSSHLocalPortForwarding)
|
|
||||||
}
|
|
||||||
if got.EnableSSHRemotePortForwarding != false {
|
|
||||||
t.Fatalf("EnableSSHRemotePortForwarding not copied: got %v", got.EnableSSHRemotePortForwarding)
|
|
||||||
}
|
|
||||||
if got.DisableSSHAuth != true {
|
|
||||||
t.Fatalf("DisableSSHAuth not copied: got %v", got.DisableSSHAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure CopyFlagsFrom does not touch unrelated fields
|
|
||||||
origin.Hostname = "host-a"
|
|
||||||
got.Hostname = "host-b"
|
|
||||||
got.CopyFlagsFrom(origin)
|
|
||||||
if got.Hostname != "host-b" {
|
|
||||||
t.Fatalf("CopyFlagsFrom should not overwrite non-flag fields, got Hostname=%q", got.Hostname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_LocalWTVersion(t *testing.T) {
|
func Test_LocalWTVersion(t *testing.T) {
|
||||||
got := GetInfo(context.TODO())
|
got := GetInfo(context.TODO())
|
||||||
want := "development"
|
want := "development"
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
"github.com/netbirdio/netbird/client/internal/sleep"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/client/ui/desktop"
|
"github.com/netbirdio/netbird/client/ui/desktop"
|
||||||
"github.com/netbirdio/netbird/client/ui/event"
|
"github.com/netbirdio/netbird/client/ui/event"
|
||||||
@@ -210,11 +209,10 @@ var iconConnectedDot []byte
|
|||||||
var iconDisconnectedDot []byte
|
var iconDisconnectedDot []byte
|
||||||
|
|
||||||
type serviceClient struct {
|
type serviceClient struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
addr string
|
addr string
|
||||||
conn proto.DaemonServiceClient
|
conn proto.DaemonServiceClient
|
||||||
connLock sync.Mutex
|
|
||||||
|
|
||||||
eventHandler *eventHandler
|
eventHandler *eventHandler
|
||||||
|
|
||||||
@@ -1100,9 +1098,6 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
|
|
||||||
go s.eventManager.Start(s.ctx)
|
go s.eventManager.Start(s.ctx)
|
||||||
go s.eventHandler.listen(s.ctx)
|
go s.eventHandler.listen(s.ctx)
|
||||||
|
|
||||||
// Start sleep detection listener
|
|
||||||
go s.startSleepListener()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) attachOutput(cmd *exec.Cmd) *os.File {
|
func (s *serviceClient) attachOutput(cmd *exec.Cmd) *os.File {
|
||||||
@@ -1139,8 +1134,6 @@ func (s *serviceClient) onTrayExit() {
|
|||||||
|
|
||||||
// getSrvClient connection to the service.
|
// getSrvClient connection to the service.
|
||||||
func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonServiceClient, error) {
|
func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonServiceClient, error) {
|
||||||
s.connLock.Lock()
|
|
||||||
defer s.connLock.Unlock()
|
|
||||||
if s.conn != nil {
|
if s.conn != nil {
|
||||||
return s.conn, nil
|
return s.conn, nil
|
||||||
}
|
}
|
||||||
@@ -1163,62 +1156,6 @@ func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonService
|
|||||||
return s.conn, nil
|
return s.conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startSleepListener initializes the sleep detection service and listens for sleep events
|
|
||||||
func (s *serviceClient) startSleepListener() {
|
|
||||||
sleepService, err := sleep.New()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("%v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sleepService.Register(s.handleSleepEvents); err != nil {
|
|
||||||
log.Errorf("failed to start sleep detection: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("sleep detection service initialized")
|
|
||||||
|
|
||||||
// Cleanup on context cancellation
|
|
||||||
go func() {
|
|
||||||
<-s.ctx.Done()
|
|
||||||
log.Info("stopping sleep event listener")
|
|
||||||
if err := sleepService.Deregister(); err != nil {
|
|
||||||
log.Errorf("failed to deregister sleep detection: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSleepEvents sends a sleep notification to the daemon via gRPC
|
|
||||||
func (s *serviceClient) handleSleepEvents(event sleep.EventType) {
|
|
||||||
conn, err := s.getSrvClient(0)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to get daemon client for sleep notification: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &proto.OSLifecycleRequest{}
|
|
||||||
|
|
||||||
switch event {
|
|
||||||
case sleep.EventTypeWakeUp:
|
|
||||||
log.Infof("handle wakeup event: %v", event)
|
|
||||||
req.Type = proto.OSLifecycleRequest_WAKEUP
|
|
||||||
case sleep.EventTypeSleep:
|
|
||||||
log.Infof("handle sleep event: %v", event)
|
|
||||||
req.Type = proto.OSLifecycleRequest_SLEEP
|
|
||||||
default:
|
|
||||||
log.Infof("unknown event: %v", event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.NotifyOSLifecycle(s.ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to notify daemon about os lifecycle notification: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("successfully notified daemon about os lifecycle")
|
|
||||||
}
|
|
||||||
|
|
||||||
// setSettingsEnabled enables or disables the settings menu based on the provided state
|
// setSettingsEnabled enables or disables the settings menu based on the provided state
|
||||||
func (s *serviceClient) setSettingsEnabled(enabled bool) {
|
func (s *serviceClient) setSettingsEnabled(enabled bool) {
|
||||||
if s.mSettings != nil {
|
if s.mSettings != nil {
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ import (
|
|||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
|
||||||
uptypes "github.com/netbirdio/netbird/upload-server/types"
|
uptypes "github.com/netbirdio/netbird/upload-server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -291,19 +289,18 @@ func (s *serviceClient) handleRunForDuration(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
statusOutput, err := s.collectDebugData(conn, initialState, params, progressUI)
|
defer s.restoreServiceState(conn, initialState)
|
||||||
if err != nil {
|
|
||||||
|
if err := s.collectDebugData(conn, initialState, params, progressUI); err != nil {
|
||||||
handleError(progressUI, err.Error())
|
handleError(progressUI, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.createDebugBundleFromCollection(conn, params, statusOutput, progressUI); err != nil {
|
if err := s.createDebugBundleFromCollection(conn, params, progressUI); err != nil {
|
||||||
handleError(progressUI, err.Error())
|
handleError(progressUI, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.restoreServiceState(conn, initialState)
|
|
||||||
|
|
||||||
progressUI.statusLabel.SetText("Bundle created successfully")
|
progressUI.statusLabel.SetText("Bundle created successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,68 +414,33 @@ func (s *serviceClient) collectDebugData(
|
|||||||
state *debugInitialState,
|
state *debugInitialState,
|
||||||
params *debugCollectionParams,
|
params *debugCollectionParams,
|
||||||
progress *progressUI,
|
progress *progressUI,
|
||||||
) (string, error) {
|
) error {
|
||||||
ctx, cancel := context.WithTimeout(s.ctx, params.duration)
|
ctx, cancel := context.WithTimeout(s.ctx, params.duration)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
startProgressTracker(ctx, &wg, params.duration, progress)
|
startProgressTracker(ctx, &wg, params.duration, progress)
|
||||||
|
|
||||||
if err := s.configureServiceForDebug(conn, state, params.enablePersistence); err != nil {
|
if err := s.configureServiceForDebug(conn, state, params.enablePersistence); err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm := profilemanager.NewProfileManager()
|
|
||||||
var profName string
|
|
||||||
if activeProf, err := pm.GetActiveProfile(); err == nil {
|
|
||||||
profName = activeProf.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
postUpStatus, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to get post-up status: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var postUpStatusOutput string
|
|
||||||
if postUpStatus != nil {
|
|
||||||
overview := nbstatus.ConvertToStatusOutputOverview(postUpStatus, params.anonymize, "", nil, nil, nil, "", profName)
|
|
||||||
postUpStatusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
|
||||||
}
|
|
||||||
headerPostUp := fmt.Sprintf("----- NetBird post-up - Timestamp: %s", time.Now().Format(time.RFC3339))
|
|
||||||
statusOutput := fmt.Sprintf("%s\n%s", headerPostUp, postUpStatusOutput)
|
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
progress.progressBar.Hide()
|
progress.progressBar.Hide()
|
||||||
progress.statusLabel.SetText("Collecting debug data...")
|
progress.statusLabel.SetText("Collecting debug data...")
|
||||||
|
|
||||||
preDownStatus, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
return nil
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to get pre-down status: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var preDownStatusOutput string
|
|
||||||
if preDownStatus != nil {
|
|
||||||
overview := nbstatus.ConvertToStatusOutputOverview(preDownStatus, params.anonymize, "", nil, nil, nil, "", profName)
|
|
||||||
preDownStatusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
|
||||||
}
|
|
||||||
headerPreDown := fmt.Sprintf("----- NetBird pre-down - Timestamp: %s - Duration: %s",
|
|
||||||
time.Now().Format(time.RFC3339), params.duration)
|
|
||||||
statusOutput = fmt.Sprintf("%s\n%s\n%s", statusOutput, headerPreDown, preDownStatusOutput)
|
|
||||||
|
|
||||||
return statusOutput, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the debug bundle with collected data
|
// Create the debug bundle with collected data
|
||||||
func (s *serviceClient) createDebugBundleFromCollection(
|
func (s *serviceClient) createDebugBundleFromCollection(
|
||||||
conn proto.DaemonServiceClient,
|
conn proto.DaemonServiceClient,
|
||||||
params *debugCollectionParams,
|
params *debugCollectionParams,
|
||||||
statusOutput string,
|
|
||||||
progress *progressUI,
|
progress *progressUI,
|
||||||
) error {
|
) error {
|
||||||
progress.statusLabel.SetText("Creating debug bundle with collected logs...")
|
progress.statusLabel.SetText("Creating debug bundle with collected logs...")
|
||||||
|
|
||||||
request := &proto.DebugBundleRequest{
|
request := &proto.DebugBundleRequest{
|
||||||
Anonymize: params.anonymize,
|
Anonymize: params.anonymize,
|
||||||
Status: statusOutput,
|
|
||||||
SystemInfo: params.systemInfo,
|
SystemInfo: params.systemInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,26 +543,8 @@ func (s *serviceClient) createDebugBundle(anonymize bool, systemInfo bool, uploa
|
|||||||
return nil, fmt.Errorf("get client: %v", err)
|
return nil, fmt.Errorf("get client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pm := profilemanager.NewProfileManager()
|
|
||||||
var profName string
|
|
||||||
if activeProf, err := pm.GetActiveProfile(); err == nil {
|
|
||||||
profName = activeProf.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to get status for debug bundle: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusOutput string
|
|
||||||
if statusResp != nil {
|
|
||||||
overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil, "", profName)
|
|
||||||
statusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &proto.DebugBundleRequest{
|
request := &proto.DebugBundleRequest{
|
||||||
Anonymize: anonymize,
|
Anonymize: anonymize,
|
||||||
Status: statusOutput,
|
|
||||||
SystemInfo: systemInfo,
|
SystemInfo: systemInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ func IsAnotherProcessRunning() (int32, bool, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
runningProcessName := strings.ToLower(filepath.Base(runningProcessPath))
|
if strings.Contains(strings.ToLower(runningProcessPath), processName) && isProcessOwnedByCurrentUser(p) {
|
||||||
if runningProcessName == processName && isProcessOwnedByCurrentUser(p) {
|
|
||||||
return p.Pid, true, nil
|
return p.Pid, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
clientStartTimeout = 30 * time.Second
|
clientStartTimeout = 30 * time.Second
|
||||||
clientStopTimeout = 10 * time.Second
|
clientStopTimeout = 10 * time.Second
|
||||||
defaultLogLevel = "warn"
|
defaultLogLevel = "warn"
|
||||||
defaultSSHDetectionTimeout = 20 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -208,19 +207,11 @@ func createDetectSSHServerMethod(client *netbird.Client) js.Func {
|
|||||||
host := args[0].String()
|
host := args[0].String()
|
||||||
port := args[1].Int()
|
port := args[1].Int()
|
||||||
|
|
||||||
timeoutMs := int(defaultSSHDetectionTimeout.Milliseconds())
|
|
||||||
if len(args) >= 3 && !args[2].IsNull() && !args[2].IsUndefined() {
|
|
||||||
timeoutMs = args[2].Int()
|
|
||||||
if timeoutMs <= 0 {
|
|
||||||
return js.ValueOf("error: timeout must be positive")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createPromise(func(resolve, reject js.Value) {
|
return createPromise(func(resolve, reject js.Value) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
serverType, err := sshdetection.DetectSSHServerType(ctx, client, host, port)
|
serverType, err := detectSSHServerType(ctx, client, host, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reject.Invoke(err.Error())
|
reject.Invoke(err.Error())
|
||||||
return
|
return
|
||||||
@@ -231,6 +222,11 @@ func createDetectSSHServerMethod(client *netbird.Client) js.Func {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectSSHServerType detects SSH server type using NetBird network connection
|
||||||
|
func detectSSHServerType(ctx context.Context, client *netbird.Client, host string, port int) (sshdetection.ServerType, error) {
|
||||||
|
return sshdetection.DetectSSHServerType(ctx, client, host, port)
|
||||||
|
}
|
||||||
|
|
||||||
// createClientObject wraps the NetBird client in a JavaScript object
|
// createClientObject wraps the NetBird client in a JavaScript object
|
||||||
func createClientObject(client *netbird.Client) js.Value {
|
func createClientObject(client *netbird.Client) js.Value {
|
||||||
obj := make(map[string]interface{})
|
obj := make(map[string]interface{})
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ type CustomZone struct {
|
|||||||
Domain string
|
Domain string
|
||||||
// Records custom zone records
|
// Records custom zone records
|
||||||
Records []SimpleRecord
|
Records []SimpleRecord
|
||||||
// SearchDomainDisabled indicates whether to add match domains to a search domains list or not
|
|
||||||
SearchDomainDisabled bool
|
|
||||||
// SkipPTRProcess indicates whether a client should process PTR records from custom zones
|
|
||||||
SkipPTRProcess bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleRecord provides a simple DNS record specification for CNAME, A and AAAA records
|
// SimpleRecord provides a simple DNS record specification for CNAME, A and AAAA records
|
||||||
|
|||||||
26
go.mod
26
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/netbirdio/netbird
|
module github.com/netbirdio/netbird
|
||||||
|
|
||||||
go 1.24.10
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cunicu.li/go-rosenpass v0.4.0
|
cunicu.li/go-rosenpass v0.4.0
|
||||||
@@ -17,8 +17,8 @@ require (
|
|||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
golang.org/x/crypto v0.45.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/sys v0.38.0
|
golang.org/x/sys v0.35.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
@@ -64,8 +64,9 @@ require (
|
|||||||
github.com/mdlayher/socket v0.5.1
|
github.com/mdlayher/socket v0.5.1
|
||||||
github.com/miekg/dns v1.1.59
|
github.com/miekg/dns v1.1.59
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20251203183432-d5400f030847
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20251027212525-d751b79f5d48
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2
|
||||||
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
@@ -105,12 +106,12 @@ require (
|
|||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
goauthentik.io/api/v3 v3.2023051.3
|
goauthentik.io/api/v3 v3.2023051.3
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/mobile v0.0.0-20251113184115-a159579294ab
|
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a
|
||||||
golang.org/x/mod v0.30.0
|
golang.org/x/mod v0.26.0
|
||||||
golang.org/x/net v0.47.0
|
golang.org/x/net v0.42.0
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.18.0
|
golang.org/x/sync v0.16.0
|
||||||
golang.org/x/term v0.37.0
|
golang.org/x/term v0.34.0
|
||||||
golang.org/x/time v0.12.0
|
golang.org/x/time v0.12.0
|
||||||
google.golang.org/api v0.177.0
|
google.golang.org/api v0.177.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@@ -132,6 +133,7 @@ require (
|
|||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.12.3 // indirect
|
github.com/Microsoft/hcsshim v0.12.3 // indirect
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
github.com/awnumar/memcall v0.4.0 // indirect
|
github.com/awnumar/memcall v0.4.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
|
||||||
@@ -251,9 +253,9 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/image v0.33.0 // indirect
|
golang.org/x/image v0.24.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
golang.org/x/tools v0.39.0 // indirect
|
golang.org/x/tools v0.35.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
|
|||||||
52
go.sum
52
go.sum
@@ -27,10 +27,13 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
|||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
|
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
|
||||||
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
|
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
|
||||||
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJTa/axESA0889D3UlZbLo=
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJTa/axESA0889D3UlZbLo=
|
||||||
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I=
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g=
|
github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g=
|
||||||
github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w=
|
github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w=
|
||||||
github.com/awnumar/memguard v0.23.0 h1:sJ3a1/SWlcuKIQ7MV+R9p0Pvo9CWsMbGZvcZQtmc68A=
|
github.com/awnumar/memguard v0.23.0 h1:sJ3a1/SWlcuKIQ7MV+R9p0Pvo9CWsMbGZvcZQtmc68A=
|
||||||
@@ -75,6 +78,7 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
|||||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -298,6 +302,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
|
|||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
@@ -368,8 +373,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S
|
|||||||
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
|
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
|
||||||
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51 h1:Ov4qdafATOgGMB1wbSuh+0aAHcwz9hdvB6VZjh1mVMI=
|
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51 h1:Ov4qdafATOgGMB1wbSuh+0aAHcwz9hdvB6VZjh1mVMI=
|
||||||
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51/go.mod h1:ZSIbPdBn5hePO8CpF1PekH2SfpTxg1PDhEwtbqZS7R8=
|
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51/go.mod h1:ZSIbPdBn5hePO8CpF1PekH2SfpTxg1PDhEwtbqZS7R8=
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20251203183432-d5400f030847 h1:V0zsYYMU5d2UN1m9zOLPEZCGWpnhtkYcxQVi9Rrx3bY=
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20251027212525-d751b79f5d48 h1:moJbL1uuaWR35yUgHZ6suijjqqW8/qGCuPPBXu5MeWQ=
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20251203183432-d5400f030847/go.mod h1:qzLCKeR253jtsWhfZTt4fyegI5zei32jKZykV+oSQOo=
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20251027212525-d751b79f5d48/go.mod h1:ifKa2jGPsOzZhJFo72v2AE5nMP3GYvlhoZ9JV6lHlJ8=
|
||||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
|
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
|
||||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
||||||
@@ -383,6 +388,8 @@ github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXq
|
|||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||||
github.com/okta/okta-sdk-golang/v2 v2.18.0 h1:cfDasMb7CShbZvOrF6n+DnLevWwiHgedWMGJ8M8xKDc=
|
github.com/okta/okta-sdk-golang/v2 v2.18.0 h1:cfDasMb7CShbZvOrF6n+DnLevWwiHgedWMGJ8M8xKDc=
|
||||||
github.com/okta/okta-sdk-golang/v2 v2.18.0/go.mod h1:dz30v3ctAiMb7jpsCngGfQUAEGm1/NsWT92uTbNDQIs=
|
github.com/okta/okta-sdk-golang/v2 v2.18.0/go.mod h1:dz30v3ctAiMb7jpsCngGfQUAEGm1/NsWT92uTbNDQIs=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@@ -483,6 +490,7 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
|||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||||
@@ -600,19 +608,19 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m
|
|||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mobile v0.0.0-20251113184115-a159579294ab h1:Iqyc+2zr7aGyLuEadIm0KRJP0Wwt+fhlXLa51Fxf1+Q=
|
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
|
||||||
golang.org/x/mobile v0.0.0-20251113184115-a159579294ab/go.mod h1:Eq3Nh/5pFSWug2ohiudJ1iyU59SO78QFuh4qTTN++I0=
|
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -622,8 +630,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -647,8 +655,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
@@ -665,8 +673,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -703,8 +711,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -717,8 +725,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
|||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -730,8 +738,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -749,8 +757,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import (
|
|||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral"
|
|
||||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
|
||||||
"github.com/netbirdio/netbird/management/internals/shared/grpc"
|
"github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
"github.com/netbirdio/netbird/management/server/account"
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||||
@@ -43,14 +41,12 @@ type Controller struct {
|
|||||||
accountManagerMetrics *telemetry.AccountManagerMetrics
|
accountManagerMetrics *telemetry.AccountManagerMetrics
|
||||||
peersUpdateManager network_map.PeersUpdateManager
|
peersUpdateManager network_map.PeersUpdateManager
|
||||||
settingsManager settings.Manager
|
settingsManager settings.Manager
|
||||||
EphemeralPeersManager ephemeral.Manager
|
|
||||||
|
|
||||||
accountUpdateLocks sync.Map
|
accountUpdateLocks sync.Map
|
||||||
sendAccountUpdateLocks sync.Map
|
sendAccountUpdateLocks sync.Map
|
||||||
updateAccountPeersBufferInterval atomic.Int64
|
updateAccountPeersBufferInterval atomic.Int64
|
||||||
// dnsDomain is used for peer resolution. This is appended to the peer's name
|
// dnsDomain is used for peer resolution. This is appended to the peer's name
|
||||||
dnsDomain string
|
dnsDomain string
|
||||||
config *config.Config
|
|
||||||
|
|
||||||
requestBuffer account.RequestBuffer
|
requestBuffer account.RequestBuffer
|
||||||
|
|
||||||
@@ -72,7 +68,7 @@ type bufferUpdate struct {
|
|||||||
|
|
||||||
var _ network_map.Controller = (*Controller)(nil)
|
var _ network_map.Controller = (*Controller)(nil)
|
||||||
|
|
||||||
func NewController(ctx context.Context, store store.Store, metrics telemetry.AppMetrics, peersUpdateManager network_map.PeersUpdateManager, requestBuffer account.RequestBuffer, integratedPeerValidator integrated_validator.IntegratedValidator, settingsManager settings.Manager, dnsDomain string, proxyController port_forwarding.Controller, ephemeralPeersManager ephemeral.Manager, config *config.Config) *Controller {
|
func NewController(ctx context.Context, store store.Store, metrics telemetry.AppMetrics, peersUpdateManager network_map.PeersUpdateManager, requestBuffer account.RequestBuffer, integratedPeerValidator integrated_validator.IntegratedValidator, settingsManager settings.Manager, dnsDomain string, proxyController port_forwarding.Controller) *Controller {
|
||||||
nMetrics, err := newMetrics(metrics.UpdateChannelMetrics())
|
nMetrics, err := newMetrics(metrics.UpdateChannelMetrics())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(fmt.Errorf("error creating metrics: %w", err))
|
log.Fatal(fmt.Errorf("error creating metrics: %w", err))
|
||||||
@@ -99,10 +95,8 @@ func NewController(ctx context.Context, store store.Store, metrics telemetry.App
|
|||||||
integratedPeerValidator: integratedPeerValidator,
|
integratedPeerValidator: integratedPeerValidator,
|
||||||
settingsManager: settingsManager,
|
settingsManager: settingsManager,
|
||||||
dnsDomain: dnsDomain,
|
dnsDomain: dnsDomain,
|
||||||
config: config,
|
|
||||||
|
|
||||||
proxyController: proxyController,
|
proxyController: proxyController,
|
||||||
EphemeralPeersManager: ephemeralPeersManager,
|
|
||||||
|
|
||||||
holder: types.NewHolder(),
|
holder: types.NewHolder(),
|
||||||
expNewNetworkMap: newNetworkMapBuilder,
|
expNewNetworkMap: newNetworkMapBuilder,
|
||||||
@@ -110,31 +104,6 @@ func NewController(ctx context.Context, store store.Store, metrics telemetry.App
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) OnPeerConnected(ctx context.Context, accountID string, peerID string) (chan *network_map.UpdateMessage, error) {
|
|
||||||
peer, err := c.repo.GetPeerByID(ctx, accountID, peerID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get peer %s: %v", peerID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.EphemeralPeersManager.OnPeerConnected(ctx, peer)
|
|
||||||
|
|
||||||
return c.peersUpdateManager.CreateChannel(ctx, peerID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) OnPeerDisconnected(ctx context.Context, accountID string, peerID string) {
|
|
||||||
c.peersUpdateManager.CloseChannel(ctx, peerID)
|
|
||||||
peer, err := c.repo.GetPeerByID(ctx, accountID, peerID)
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(ctx).Errorf("failed to get peer %s: %v", peerID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.EphemeralPeersManager.OnPeerDisconnected(ctx, peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) CountStreams() int {
|
|
||||||
return c.peersUpdateManager.CountStreams()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID string) error {
|
func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID string) error {
|
||||||
log.WithContext(ctx).Tracef("updating peers for account %s from %s", accountID, util.GetCallerName())
|
log.WithContext(ctx).Tracef("updating peers for account %s from %s", accountID, util.GetCallerName())
|
||||||
var (
|
var (
|
||||||
@@ -236,7 +205,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
|
|||||||
|
|
||||||
peerGroups := account.GetPeerGroups(p.ID)
|
peerGroups := account.GetPeerGroups(p.ID)
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
update := grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, p, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSetting, maps.Keys(peerGroups), dnsFwdPort)
|
update := grpc.ToSyncResponse(ctx, nil, p, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSetting, maps.Keys(peerGroups), dnsFwdPort)
|
||||||
c.metrics.CountToSyncResponseDuration(time.Since(start))
|
c.metrics.CountToSyncResponseDuration(time.Since(start))
|
||||||
|
|
||||||
c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{Update: update})
|
c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{Update: update})
|
||||||
@@ -354,7 +323,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe
|
|||||||
peerGroups := account.GetPeerGroups(peerId)
|
peerGroups := account.GetPeerGroups(peerId)
|
||||||
dnsFwdPort := computeForwarderPort(maps.Values(account.Peers), network_map.DnsForwarderPortMinVersion)
|
dnsFwdPort := computeForwarderPort(maps.Values(account.Peers), network_map.DnsForwarderPortMinVersion)
|
||||||
|
|
||||||
update := grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, peer, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSettings, maps.Keys(peerGroups), dnsFwdPort)
|
update := grpc.ToSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSettings, maps.Keys(peerGroups), dnsFwdPort)
|
||||||
c.peersUpdateManager.SendUpdate(ctx, peer.ID, &network_map.UpdateMessage{Update: update})
|
c.peersUpdateManager.SendUpdate(ctx, peer.ID, &network_map.UpdateMessage{Update: update})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -394,26 +363,55 @@ func (c *Controller) BufferUpdateAccountPeers(ctx context.Context, accountID str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetValidatedPeerWithMap(ctx context.Context, isRequiresApproval bool, accountID string, peer *nbpeer.Peer, clientSerial uint64) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, int64, error) {
|
func (c *Controller) DeletePeer(ctx context.Context, accountId string, peerId string) error {
|
||||||
network, err := c.repo.GetAccountNetwork(ctx, accountID)
|
network, err := c.repo.GetAccountNetwork(ctx, accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peers, err := c.repo.GetAccountPeers(ctx, accountId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsFwdPort := computeForwarderPort(peers, network_map.DnsForwarderPortMinVersion)
|
||||||
|
c.peersUpdateManager.SendUpdate(ctx, peerId, &network_map.UpdateMessage{
|
||||||
|
Update: &proto.SyncResponse{
|
||||||
|
RemotePeers: []*proto.RemotePeerConfig{},
|
||||||
|
RemotePeersIsEmpty: true,
|
||||||
|
NetworkMap: &proto.NetworkMap{
|
||||||
|
Serial: network.CurrentSerial(),
|
||||||
|
RemotePeers: []*proto.RemotePeerConfig{},
|
||||||
|
RemotePeersIsEmpty: true,
|
||||||
|
FirewallRules: []*proto.FirewallRule{},
|
||||||
|
FirewallRulesIsEmpty: true,
|
||||||
|
DNSConfig: &proto.DNSConfig{
|
||||||
|
ForwarderPort: dnsFwdPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
c.peersUpdateManager.CloseChannel(ctx, peerId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GetValidatedPeerWithMap(ctx context.Context, isRequiresApproval bool, accountID string, peer *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, int64, error) {
|
||||||
if isRequiresApproval {
|
if isRequiresApproval {
|
||||||
|
network, err := c.repo.GetAccountNetwork(ctx, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
emptyMap := &types.NetworkMap{
|
emptyMap := &types.NetworkMap{
|
||||||
Network: network.Copy(),
|
Network: network.Copy(),
|
||||||
}
|
}
|
||||||
return peer, emptyMap, nil, 0, nil
|
return peer, emptyMap, nil, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientSerial > 0 && clientSerial == network.CurrentSerial() {
|
var (
|
||||||
log.WithContext(ctx).Debugf("client serial %d matches current serial, skipping network map calculation", clientSerial)
|
account *types.Account
|
||||||
return peer, nil, nil, 0, nil
|
err error
|
||||||
}
|
)
|
||||||
|
|
||||||
var account *types.Account
|
|
||||||
|
|
||||||
if c.experimentalNetworkMap(accountID) {
|
if c.experimentalNetworkMap(accountID) {
|
||||||
account = c.getAccountFromHolderOrInit(accountID)
|
account = c.getAccountFromHolderOrInit(accountID)
|
||||||
} else {
|
} else {
|
||||||
@@ -697,83 +695,35 @@ func isPeerInPolicySourceGroups(account *types.Account, peerID string, policy *t
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) OnPeersUpdated(ctx context.Context, accountID string, peerIDs []string) error {
|
func (c *Controller) OnPeerUpdated(accountId string, peer *nbpeer.Peer) {
|
||||||
peers, err := c.repo.GetPeersByIDs(ctx, accountID, peerIDs)
|
c.UpdatePeerInNetworkMapCache(accountId, peer)
|
||||||
if err != nil {
|
_ = c.bufferSendUpdateAccountPeers(context.Background(), accountId)
|
||||||
return fmt.Errorf("failed to get peers by ids: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, peer := range peers {
|
|
||||||
c.UpdatePeerInNetworkMapCache(accountID, peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.bufferSendUpdateAccountPeers(ctx, accountID)
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(ctx).Errorf("failed to buffer update account peers for peer update in account %s: %v", accountID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) OnPeersAdded(ctx context.Context, accountID string, peerIDs []string) error {
|
func (c *Controller) OnPeerAdded(ctx context.Context, accountID string, peerID string) error {
|
||||||
for _, peerID := range peerIDs {
|
if c.experimentalNetworkMap(accountID) {
|
||||||
if c.experimentalNetworkMap(accountID) {
|
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
|
||||||
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = c.onPeerAddedUpdNetworkMapCache(account, peerID)
|
err = c.onPeerAddedUpdNetworkMapCache(account, peerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.bufferSendUpdateAccountPeers(ctx, accountID)
|
return c.bufferSendUpdateAccountPeers(ctx, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) OnPeersDeleted(ctx context.Context, accountID string, peerIDs []string) error {
|
func (c *Controller) OnPeerDeleted(ctx context.Context, accountID string, peerID string) error {
|
||||||
network, err := c.repo.GetAccountNetwork(ctx, accountID)
|
if c.experimentalNetworkMap(accountID) {
|
||||||
if err != nil {
|
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
peers, err := c.repo.GetAccountPeers(ctx, accountID)
|
err = c.onPeerDeletedUpdNetworkMapCache(account, peerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
dnsFwdPort := computeForwarderPort(peers, network_map.DnsForwarderPortMinVersion)
|
|
||||||
for _, peerID := range peerIDs {
|
|
||||||
c.peersUpdateManager.SendUpdate(ctx, peerID, &network_map.UpdateMessage{
|
|
||||||
Update: &proto.SyncResponse{
|
|
||||||
RemotePeers: []*proto.RemotePeerConfig{},
|
|
||||||
RemotePeersIsEmpty: true,
|
|
||||||
NetworkMap: &proto.NetworkMap{
|
|
||||||
Serial: network.CurrentSerial(),
|
|
||||||
RemotePeers: []*proto.RemotePeerConfig{},
|
|
||||||
RemotePeersIsEmpty: true,
|
|
||||||
FirewallRules: []*proto.FirewallRule{},
|
|
||||||
FirewallRulesIsEmpty: true,
|
|
||||||
DNSConfig: &proto.DNSConfig{
|
|
||||||
ForwarderPort: dnsFwdPort,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
c.peersUpdateManager.CloseChannel(ctx, peerID)
|
|
||||||
|
|
||||||
if c.experimentalNetworkMap(accountID) {
|
|
||||||
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(ctx).Errorf("failed to get account %s: %v", accountID, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = c.onPeerDeletedUpdNetworkMapCache(account, peerID)
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(ctx).Errorf("failed to update network map cache for deleted peer %s in account %s: %v", peerID, accountID, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,6 +775,10 @@ func (c *Controller) GetNetworkMap(ctx context.Context, peerID string) (*types.N
|
|||||||
return networkMap, nil
|
return networkMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) DisconnectPeers(ctx context.Context, accountId string, peerIDs []string) {
|
func (c *Controller) DisconnectPeers(ctx context.Context, peerIDs []string) {
|
||||||
c.peersUpdateManager.CloseChannels(ctx, peerIDs)
|
c.peersUpdateManager.CloseChannels(ctx, peerIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) IsConnected(peerID string) bool {
|
||||||
|
return c.peersUpdateManager.HasChannel(peerID)
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user