mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-24 03:06:38 +00:00
Compare commits
101 Commits
snyk-fix-c
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcdd7fac51 | ||
|
|
ba7793ae7b | ||
|
|
e7b5537dcc | ||
|
|
95794f53ce | ||
|
|
9bcd3ebed4 | ||
|
|
b85045e723 | ||
|
|
4d7e59f199 | ||
|
|
b5daec3b51 | ||
|
|
5e1a40c33f | ||
|
|
6d6333058c | ||
|
|
e8d301fdc9 | ||
|
|
17bab881f7 | ||
|
|
25ed58328a | ||
|
|
644ed4b934 | ||
|
|
446aded1f7 | ||
|
|
acec87dd45 | ||
|
|
58faa341d2 | ||
|
|
5853b5553c | ||
|
|
998fb30e1e | ||
|
|
e254b4cde5 | ||
|
|
ead1c618ba | ||
|
|
55126f990c | ||
|
|
90577682e4 | ||
|
|
dc30dcacce | ||
|
|
2c87fa6236 | ||
|
|
ec8d83ade4 | ||
|
|
3130cce72d | ||
|
|
bd23ab925e | ||
|
|
0c6f671a7c | ||
|
|
cf7f6c355f | ||
|
|
47e64d72db | ||
|
|
9e81e782e5 | ||
|
|
7aef0f67df | ||
|
|
dba7ef667d | ||
|
|
69d87343d2 | ||
|
|
5113c70943 | ||
|
|
ad8fcda67b | ||
|
|
d33f88df82 | ||
|
|
786ca6fc79 | ||
|
|
dfebdf1444 | ||
|
|
a8dcff69c2 | ||
|
|
71e944fa57 | ||
|
|
d39fcfd62a | ||
|
|
21368b38d9 | ||
|
|
d817584f52 | ||
|
|
4d3dc3475d | ||
|
|
6fc50a438f | ||
|
|
149559a06b | ||
|
|
e14c6de203 | ||
|
|
d4c067f0af | ||
|
|
3f6d95552f | ||
|
|
dbefa8bd9f | ||
|
|
d4ac7f8df9 | ||
|
|
4fd10b9447 | ||
|
|
aa595c3073 | ||
|
|
99bd34c02a | ||
|
|
7ce5507c05 | ||
|
|
0320bb7b35 | ||
|
|
f063866ce8 | ||
|
|
9f84165763 | ||
|
|
3488a516c9 | ||
|
|
5e273c121a | ||
|
|
968d95698e | ||
|
|
28bef26537 | ||
|
|
0d2845ea31 | ||
|
|
f425870c8e | ||
|
|
f9d64a06c2 | ||
|
|
86555c44f7 | ||
|
|
48792c64cd | ||
|
|
533d93eb17 | ||
|
|
9685411246 | ||
|
|
d00a226556 | ||
|
|
5d361b5421 | ||
|
|
a889c4108b | ||
|
|
12cad854b2 | ||
|
|
6a3846a8b7 | ||
|
|
7cd5dcae59 | ||
|
|
0e62325d46 | ||
|
|
b3056d0937 | ||
|
|
ab853ac2a5 | ||
|
|
e97f853909 | ||
|
|
70db8751d7 | ||
|
|
86a00ab4af | ||
|
|
3d4b502126 | ||
|
|
a4e8647aef | ||
|
|
160b811e21 | ||
|
|
5e607cf4e9 | ||
|
|
0fdb944058 | ||
|
|
ccbabd9e2a | ||
|
|
a942e4add5 | ||
|
|
1022a5015c | ||
|
|
375fcf2752 | ||
|
|
9acf7f9262 | ||
|
|
82937ba184 | ||
|
|
0f52144894 | ||
|
|
0926400b8a | ||
|
|
bef99d48f8 | ||
|
|
9e95841252 | ||
|
|
6da3943559 | ||
|
|
f5b4659adb | ||
|
|
3d19468b6c |
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@@ -12,6 +12,16 @@
|
|||||||
- [ ] Is a feature enhancement
|
- [ ] Is a feature enhancement
|
||||||
- [ ] It is a refactor
|
- [ ] It is a refactor
|
||||||
- [ ] Created tests that fail without the change (if possible)
|
- [ ] Created tests that fail without the change (if possible)
|
||||||
- [ ] Extended the README / documentation, if necessary
|
|
||||||
|
|
||||||
> By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).
|
> By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
Select exactly one:
|
||||||
|
|
||||||
|
- [ ] I added/updated documentation for this change
|
||||||
|
- [ ] Documentation is **not needed** for this change (explain why)
|
||||||
|
|
||||||
|
### Docs PR URL (required if "docs added" is checked)
|
||||||
|
Paste the PR link from https://github.com/netbirdio/docs here:
|
||||||
|
|
||||||
|
https://github.com/netbirdio/docs/pull/__
|
||||||
|
|||||||
94
.github/workflows/docs-ack.yml
vendored
Normal file
94
.github/workflows/docs-ack.yml
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
name: Docs Acknowledgement
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs-ack:
|
||||||
|
name: Require docs PR URL or explicit "not needed"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Read PR body
|
||||||
|
id: body
|
||||||
|
run: |
|
||||||
|
BODY=$(jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH")
|
||||||
|
echo "body<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$BODY" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Validate checkbox selection
|
||||||
|
id: validate
|
||||||
|
run: |
|
||||||
|
body='${{ steps.body.outputs.body }}'
|
||||||
|
|
||||||
|
added_checked=$(printf "%s" "$body" | grep -E '^- \[x\] I added/updated documentation' -i | wc -l | tr -d ' ')
|
||||||
|
noneed_checked=$(printf "%s" "$body" | grep -E '^- \[x\] Documentation is \*\*not needed\*\*' -i | wc -l | tr -d ' ')
|
||||||
|
|
||||||
|
if [ "$added_checked" -eq 1 ] && [ "$noneed_checked" -eq 1 ]; then
|
||||||
|
echo "::error::Choose exactly one: either 'docs added' OR 'not needed'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$added_checked" -eq 0 ] && [ "$noneed_checked" -eq 0 ]; then
|
||||||
|
echo "::error::You must check exactly one docs option in the PR template."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$added_checked" -eq 1 ]; then
|
||||||
|
echo "mode=added" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "mode=noneed" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Extract docs PR URL (when 'docs added')
|
||||||
|
if: steps.validate.outputs.mode == 'added'
|
||||||
|
id: extract
|
||||||
|
run: |
|
||||||
|
body='${{ steps.body.outputs.body }}'
|
||||||
|
|
||||||
|
# Strictly require HTTPS and that it's a PR in netbirdio/docs
|
||||||
|
# Examples accepted:
|
||||||
|
# https://github.com/netbirdio/docs/pull/1234
|
||||||
|
url=$(printf "%s" "$body" | grep -Eo 'https://github\.com/netbirdio/docs/pull/[0-9]+' | head -n1 || true)
|
||||||
|
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
echo "::error::You checked 'docs added' but didn't include a valid HTTPS PR link to netbirdio/docs (e.g., https://github.com/netbirdio/docs/pull/1234)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pr_number=$(echo "$url" | sed -E 's#.*/pull/([0-9]+)$#\1#')
|
||||||
|
echo "url=$url" >> $GITHUB_OUTPUT
|
||||||
|
echo "pr_number=$pr_number" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Verify docs PR exists (and is open or merged)
|
||||||
|
if: steps.validate.outputs.mode == 'added'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
id: verify
|
||||||
|
with:
|
||||||
|
pr_number: ${{ steps.extract.outputs.pr_number }}
|
||||||
|
script: |
|
||||||
|
const prNumber = parseInt(core.getInput('pr_number'), 10);
|
||||||
|
const { data } = await github.rest.pulls.get({
|
||||||
|
owner: 'netbirdio',
|
||||||
|
repo: 'docs',
|
||||||
|
pull_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow open or merged PRs
|
||||||
|
const ok = data.state === 'open' || data.merged === true;
|
||||||
|
core.setOutput('state', data.state);
|
||||||
|
core.setOutput('merged', String(!!data.merged));
|
||||||
|
if (!ok) {
|
||||||
|
core.setFailed(`Docs PR #${prNumber} exists but is neither open nor merged (state=${data.state}, merged=${data.merged}).`);
|
||||||
|
}
|
||||||
|
result-encoding: string
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: All good
|
||||||
|
run: echo "Documentation requirement satisfied ✅"
|
||||||
18
.github/workflows/forum.yml
vendored
Normal file
18
.github/workflows/forum.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Post release topic on Discourse
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
post:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: roots/discourse-topic-github-release-action@main
|
||||||
|
with:
|
||||||
|
discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }}
|
||||||
|
discourse-base-url: https://forum.netbird.io
|
||||||
|
discourse-author-username: NetBird
|
||||||
|
discourse-category: 17
|
||||||
|
discourse-tags:
|
||||||
|
releases
|
||||||
3
.github/workflows/golang-test-freebsd.yml
vendored
3
.github/workflows/golang-test-freebsd.yml
vendored
@@ -25,8 +25,7 @@ jobs:
|
|||||||
release: "14.2"
|
release: "14.2"
|
||||||
prepare: |
|
prepare: |
|
||||||
pkg install -y curl pkgconf xorg
|
pkg install -y curl pkgconf xorg
|
||||||
LATEST_VERSION=$(curl -s https://go.dev/VERSION?m=text|head -n 1)
|
GO_TARBALL="go1.23.12.freebsd-amd64.tar.gz"
|
||||||
GO_TARBALL="$LATEST_VERSION.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"
|
||||||
|
|||||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: codespell
|
- name: codespell
|
||||||
uses: codespell-project/actions-codespell@v2
|
uses: codespell-project/actions-codespell@v2
|
||||||
with:
|
with:
|
||||||
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe
|
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros
|
||||||
skip: go.mod,go.sum
|
skip: go.mod,go.sum
|
||||||
golangci:
|
golangci:
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SIGN_PIPE_VER: "v0.0.21"
|
SIGN_PIPE_VER: "v0.0.22"
|
||||||
GORELEASER_VER: "v2.3.2"
|
GORELEASER_VER: "v2.3.2"
|
||||||
PRODUCT_NAME: "NetBird"
|
PRODUCT_NAME: "NetBird"
|
||||||
COPYRIGHT: "NetBird GmbH"
|
COPYRIGHT: "NetBird GmbH"
|
||||||
@@ -79,6 +79,8 @@ jobs:
|
|||||||
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
||||||
- name: Generate windows syso amd64
|
- name: Generate windows syso amd64
|
||||||
run: goversioninfo -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
|
run: goversioninfo -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
|
||||||
|
- name: Generate windows syso arm64
|
||||||
|
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
@@ -154,10 +156,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q libappindicator3-dev gir1.2-appindicator3-0.1 libxxf86vm-dev gcc-mingw-w64-x86-64
|
run: sudo apt update && sudo apt install -y -q libappindicator3-dev gir1.2-appindicator3-0.1 libxxf86vm-dev gcc-mingw-w64-x86-64
|
||||||
|
|
||||||
|
- name: Install LLVM-MinGW for ARM64 cross-compilation
|
||||||
|
run: |
|
||||||
|
cd /tmp
|
||||||
|
wget -q https://github.com/mstorsjo/llvm-mingw/releases/download/20250709/llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64.tar.xz
|
||||||
|
echo "60cafae6474c7411174cff1d4ba21a8e46cadbaeb05a1bace306add301628337 llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64.tar.xz" | sha256sum -c
|
||||||
|
tar -xf llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64.tar.xz
|
||||||
|
echo "/tmp/llvm-mingw-20250709-ucrt-ubuntu-22.04-x86_64/bin" >> $GITHUB_PATH
|
||||||
- name: Install goversioninfo
|
- name: Install goversioninfo
|
||||||
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
||||||
- name: Generate windows syso amd64
|
- name: Generate windows syso amd64
|
||||||
run: goversioninfo -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
|
run: goversioninfo -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
|
||||||
|
- name: Generate windows syso arm64
|
||||||
|
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_arm64.syso
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v4
|
||||||
@@ -231,17 +243,3 @@ jobs:
|
|||||||
ref: ${{ env.SIGN_PIPE_VER }}
|
ref: ${{ env.SIGN_PIPE_VER }}
|
||||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||||
inputs: '{ "tag": "${{ github.ref }}", "skipRelease": false }'
|
inputs: '{ "tag": "${{ github.ref }}", "skipRelease": false }'
|
||||||
|
|
||||||
post_on_forum:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
needs: [trigger_signer]
|
|
||||||
steps:
|
|
||||||
- uses: Codixer/discourse-topic-github-release-action@v2.0.1
|
|
||||||
with:
|
|
||||||
discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }}
|
|
||||||
discourse-base-url: https://forum.netbird.io
|
|
||||||
discourse-author-username: NetBird
|
|
||||||
discourse-category: 17
|
|
||||||
discourse-tags:
|
|
||||||
releases
|
|
||||||
|
|||||||
@@ -83,6 +83,15 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup MySQL privileges
|
||||||
|
if: matrix.store == 'mysql'
|
||||||
|
run: |
|
||||||
|
sleep 10
|
||||||
|
mysql -h 127.0.0.1 -u root -pmysqlroot -e "
|
||||||
|
GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO 'netbird'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
"
|
||||||
|
|
||||||
- name: cp setup.env
|
- name: cp setup.env
|
||||||
run: cp infrastructure_files/tests/setup.env infrastructure_files/
|
run: cp infrastructure_files/tests/setup.env infrastructure_files/
|
||||||
|
|
||||||
|
|||||||
67
.github/workflows/wasm-build-validation.yml
vendored
Normal file
67
.github/workflows/wasm-build-validation.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
name: Wasm
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
js_lint:
|
||||||
|
name: "JS / Lint"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.23.x"
|
||||||
|
- 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
|
||||||
|
- name: Install golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@d6238b002a20823d52840fda27e2d4891c5952dc
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
install-mode: binary
|
||||||
|
skip-cache: true
|
||||||
|
skip-pkg-cache: true
|
||||||
|
skip-build-cache: true
|
||||||
|
- name: Run golangci-lint for WASM
|
||||||
|
run: |
|
||||||
|
GOOS=js GOARCH=wasm golangci-lint run --timeout=12m --out-format colored-line-number ./client/...
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
js_build:
|
||||||
|
name: "JS / Build"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.23.x"
|
||||||
|
- name: Build Wasm client
|
||||||
|
run: GOOS=js GOARCH=wasm go build -o netbird.wasm ./client/wasm/cmd
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
- name: Check Wasm build size
|
||||||
|
run: |
|
||||||
|
echo "Wasm build size:"
|
||||||
|
ls -lh netbird.wasm
|
||||||
|
|
||||||
|
SIZE=$(stat -c%s netbird.wasm)
|
||||||
|
SIZE_MB=$((SIZE / 1024 / 1024))
|
||||||
|
|
||||||
|
echo "Size: ${SIZE} bytes (${SIZE_MB} MB)"
|
||||||
|
|
||||||
|
if [ ${SIZE} -gt 52428800 ]; then
|
||||||
|
echo "Wasm binary size (${SIZE_MB}MB) exceeds 50MB limit!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
@@ -2,6 +2,18 @@ version: 2
|
|||||||
|
|
||||||
project_name: netbird
|
project_name: netbird
|
||||||
builds:
|
builds:
|
||||||
|
- id: netbird-wasm
|
||||||
|
dir: client/wasm/cmd
|
||||||
|
binary: netbird
|
||||||
|
env: [GOOS=js, GOARCH=wasm, CGO_ENABLED=0]
|
||||||
|
goos:
|
||||||
|
- js
|
||||||
|
goarch:
|
||||||
|
- wasm
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||||
|
|
||||||
- id: netbird
|
- id: netbird
|
||||||
dir: client
|
dir: client
|
||||||
binary: netbird
|
binary: netbird
|
||||||
@@ -16,8 +28,6 @@ builds:
|
|||||||
- arm64
|
- arm64
|
||||||
- 386
|
- 386
|
||||||
ignore:
|
ignore:
|
||||||
- goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: windows
|
- goos: windows
|
||||||
@@ -117,6 +127,11 @@ archives:
|
|||||||
- builds:
|
- builds:
|
||||||
- netbird
|
- netbird
|
||||||
- netbird-static
|
- netbird-static
|
||||||
|
- id: netbird-wasm
|
||||||
|
builds:
|
||||||
|
- netbird-wasm
|
||||||
|
name_template: "{{ .ProjectName }}_{{ .Version }}"
|
||||||
|
format: binary
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: Netbird <dev@netbird.io>
|
- maintainer: Netbird <dev@netbird.io>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ builds:
|
|||||||
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||||
|
|
||||||
- id: netbird-ui-windows
|
- id: netbird-ui-windows-amd64
|
||||||
dir: client/ui
|
dir: client/ui
|
||||||
binary: netbird-ui
|
binary: netbird-ui
|
||||||
env:
|
env:
|
||||||
@@ -30,6 +30,22 @@ builds:
|
|||||||
- -H windowsgui
|
- -H windowsgui
|
||||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||||
|
|
||||||
|
- id: netbird-ui-windows-arm64
|
||||||
|
dir: client/ui
|
||||||
|
binary: netbird-ui
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=aarch64-w64-mingw32-clang
|
||||||
|
- CXX=aarch64-w64-mingw32-clang++
|
||||||
|
goos:
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
- -H windowsgui
|
||||||
|
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: linux-arch
|
- id: linux-arch
|
||||||
name_template: "{{ .ProjectName }}-linux_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
name_template: "{{ .ProjectName }}-linux_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
@@ -38,7 +54,8 @@ archives:
|
|||||||
- id: windows-arch
|
- id: windows-arch
|
||||||
name_template: "{{ .ProjectName }}-windows_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
name_template: "{{ .ProjectName }}-windows_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
builds:
|
builds:
|
||||||
- netbird-ui-windows
|
- netbird-ui-windows-amd64
|
||||||
|
- netbird-ui-windows-arm64
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: Netbird <dev@netbird.io>
|
- maintainer: Netbird <dev@netbird.io>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package android
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -111,8 +112,8 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
|
|||||||
|
|
||||||
// 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, dns.items, dnsReadyListener)
|
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).
|
||||||
@@ -137,8 +138,8 @@ 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, dns.items, dnsReadyListener)
|
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
|
||||||
@@ -235,7 +236,7 @@ func (c *Client) OnUpdatedHostDNS(list *DNSList) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsServer.OnUpdatedHostDNSServer(list.items)
|
dnsServer.OnUpdatedHostDNSServer(slices.Clone(list.items))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
package android
|
package android
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
// DNSList is a wrapper of []string
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSList is a wrapper of []netip.AddrPort with default DNS port
|
||||||
type DNSList struct {
|
type DNSList struct {
|
||||||
items []string
|
items []netip.AddrPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new DNS address to the collection
|
// Add new DNS address to the collection, returns error if invalid
|
||||||
func (array *DNSList) Add(s string) {
|
func (array *DNSList) Add(s string) error {
|
||||||
array.items = append(array.items, s)
|
addr, err := netip.ParseAddr(s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid DNS address: %s", s)
|
||||||
|
}
|
||||||
|
addrPort := netip.AddrPortFrom(addr.Unmap(), dns.DefaultPort)
|
||||||
|
array.items = append(array.items, addrPort)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get return an element of the collection
|
// Get return an element of the collection as string
|
||||||
func (array *DNSList) Get(i int) (string, error) {
|
func (array *DNSList) Get(i int) (string, error) {
|
||||||
if i >= len(array.items) || i < 0 {
|
if i >= len(array.items) || i < 0 {
|
||||||
return "", fmt.Errorf("out of range")
|
return "", fmt.Errorf("out of range")
|
||||||
}
|
}
|
||||||
return array.items[i], nil
|
return array.items[i].Addr().String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size return with the size of the collection
|
// Size return with the size of the collection
|
||||||
|
|||||||
@@ -3,20 +3,30 @@ package android
|
|||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestDNSList_Get(t *testing.T) {
|
func TestDNSList_Get(t *testing.T) {
|
||||||
l := DNSList{
|
l := DNSList{}
|
||||||
items: make([]string, 1),
|
|
||||||
|
// Add a valid DNS address
|
||||||
|
err := l.Add("8.8.8.8")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := l.Get(0)
|
// Test getting valid index
|
||||||
|
addr, err := l.Get(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("invalid error: %s", err)
|
t.Errorf("invalid error: %s", err)
|
||||||
}
|
}
|
||||||
|
if addr != "8.8.8.8" {
|
||||||
|
t.Errorf("expected 8.8.8.8, got %s", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test negative index
|
||||||
_, err = l.Get(-1)
|
_, err = l.Get(-1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error but got nil")
|
t.Errorf("expected error but got nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test out of bounds index
|
||||||
_, err = l.Get(1)
|
_, err = l.Get(1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error but got nil")
|
t.Errorf("expected error but got nil")
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ var (
|
|||||||
var debugCmd = &cobra.Command{
|
var debugCmd = &cobra.Command{
|
||||||
Use: "debug",
|
Use: "debug",
|
||||||
Short: "Debugging commands",
|
Short: "Debugging commands",
|
||||||
Long: "Provides commands for debugging and logging control within the NetBird daemon.",
|
Long: "Commands for debugging and logging within the NetBird daemon.",
|
||||||
}
|
}
|
||||||
|
|
||||||
var debugBundleCmd = &cobra.Command{
|
var debugBundleCmd = &cobra.Command{
|
||||||
@@ -308,7 +308,7 @@ func getStatusOutput(cmd *cobra.Command, anon bool) string {
|
|||||||
cmd.PrintErrf("Failed to get status: %v\n", err)
|
cmd.PrintErrf("Failed to get status: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
statusOutputString = nbstatus.ParseToFullDetailSummary(
|
statusOutputString = nbstatus.ParseToFullDetailSummary(
|
||||||
nbstatus.ConvertToStatusOutputOverview(statusResp, anon, "", nil, nil, nil, "", ""),
|
nbstatus.ConvertToStatusOutputOverview(statusResp.GetFullStatus(), anon, statusResp.GetDaemonVersion(), "", nil, nil, nil, "", ""),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return statusOutputString
|
return statusOutputString
|
||||||
|
|||||||
8
client/cmd/debug_js.go
Normal file
8
client/cmd/debug_js.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// SetupDebugHandler is a no-op for WASM
|
||||||
|
func SetupDebugHandler(context.Context, interface{}, interface{}, interface{}, string) {
|
||||||
|
// Debug handler not needed for WASM
|
||||||
|
}
|
||||||
@@ -14,7 +14,8 @@ import (
|
|||||||
|
|
||||||
var downCmd = &cobra.Command{
|
var downCmd = &cobra.Command{
|
||||||
Use: "down",
|
Use: "down",
|
||||||
Short: "down netbird connections",
|
Short: "Disconnect from the NetBird network",
|
||||||
|
Long: "Disconnect the NetBird client from the network and management service. This will terminate all active connections with the remote peers.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
SetFlagsFromEnvVars(rootCmd)
|
SetFlagsFromEnvVars(rootCmd)
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ func init() {
|
|||||||
|
|
||||||
var loginCmd = &cobra.Command{
|
var loginCmd = &cobra.Command{
|
||||||
Use: "login",
|
Use: "login",
|
||||||
Short: "login to the NetBird Management Service (first run)",
|
Short: "Log in to the NetBird network",
|
||||||
|
Long: "Log in to the NetBird network using a setup key or SSO",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := setEnvAndFlags(cmd); err != nil {
|
if err := setEnvAndFlags(cmd); err != nil {
|
||||||
return fmt.Errorf("set env and flags: %v", err)
|
return fmt.Errorf("set env and flags: %v", err)
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var logoutCmd = &cobra.Command{
|
var logoutCmd = &cobra.Command{
|
||||||
Use: "logout",
|
Use: "deregister",
|
||||||
Short: "logout from the NetBird Management Service and delete peer",
|
Aliases: []string{"logout"},
|
||||||
|
Short: "Deregister from the NetBird management service and delete this peer",
|
||||||
|
Long: "This command will deregister the current peer from the NetBird management service and all associated configuration. Use with caution as this will remove the peer from the network.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
SetFlagsFromEnvVars(rootCmd)
|
SetFlagsFromEnvVars(rootCmd)
|
||||||
|
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*7)
|
ctx, cancel := context.WithTimeout(cmd.Context(), time.Second*15)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
@@ -44,10 +46,10 @@ var logoutCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := daemonClient.Logout(ctx, req); err != nil {
|
if _, err := daemonClient.Logout(ctx, req); err != nil {
|
||||||
return fmt.Errorf("logout: %v", err)
|
return fmt.Errorf("deregister: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Println("Logged out successfully")
|
cmd.Println("Deregistered successfully")
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var appendFlag bool
|
|||||||
var networksCMD = &cobra.Command{
|
var networksCMD = &cobra.Command{
|
||||||
Use: "networks",
|
Use: "networks",
|
||||||
Aliases: []string{"routes"},
|
Aliases: []string{"routes"},
|
||||||
Short: "Manage networks",
|
Short: "Manage connections to NetBird Networks and Resources",
|
||||||
Long: `Commands to list, select, or deselect networks. Replaces the "routes" command.`,
|
Long: `Commands to list, select, or deselect networks. Replaces the "routes" command.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import (
|
|||||||
|
|
||||||
var profileCmd = &cobra.Command{
|
var profileCmd = &cobra.Command{
|
||||||
Use: "profile",
|
Use: "profile",
|
||||||
Short: "manage NetBird profiles",
|
Short: "Manage NetBird client profiles",
|
||||||
Long: `Manage NetBird profiles, allowing you to list, switch, and remove profiles.`,
|
Long: `Commands to list, add, remove, and switch profiles. Profiles allow you to maintain different accounts in one client app.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
var profileListCmd = &cobra.Command{
|
var profileListCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "list all profiles",
|
Short: "List all profiles",
|
||||||
Long: `List all available profiles in the NetBird client.`,
|
Long: `List all available profiles in the NetBird client.`,
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
RunE: listProfilesFunc,
|
RunE: listProfilesFunc,
|
||||||
@@ -30,7 +30,7 @@ var profileListCmd = &cobra.Command{
|
|||||||
|
|
||||||
var profileAddCmd = &cobra.Command{
|
var profileAddCmd = &cobra.Command{
|
||||||
Use: "add <profile_name>",
|
Use: "add <profile_name>",
|
||||||
Short: "add a new profile",
|
Short: "Add a new profile",
|
||||||
Long: `Add a new profile to the NetBird client. The profile name must be unique.`,
|
Long: `Add a new profile to the NetBird client. The profile name must be unique.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: addProfileFunc,
|
RunE: addProfileFunc,
|
||||||
@@ -38,16 +38,16 @@ var profileAddCmd = &cobra.Command{
|
|||||||
|
|
||||||
var profileRemoveCmd = &cobra.Command{
|
var profileRemoveCmd = &cobra.Command{
|
||||||
Use: "remove <profile_name>",
|
Use: "remove <profile_name>",
|
||||||
Short: "remove a profile",
|
Short: "Remove a profile",
|
||||||
Long: `Remove a profile from the NetBird client. The profile must not be active.`,
|
Long: `Remove a profile from the NetBird client. The profile must not be inactive.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: removeProfileFunc,
|
RunE: removeProfileFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
var profileSelectCmd = &cobra.Command{
|
var profileSelectCmd = &cobra.Command{
|
||||||
Use: "select <profile_name>",
|
Use: "select <profile_name>",
|
||||||
Short: "select a profile",
|
Short: "Select a profile",
|
||||||
Long: `Select a profile to be the active profile in the NetBird client. The profile must exist.`,
|
Long: `Make the specified profile active. This will switch the client to use the selected profile's configuration.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: selectProfileFunc,
|
RunE: selectProfileFunc,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ var (
|
|||||||
dnsRouteInterval time.Duration
|
dnsRouteInterval time.Duration
|
||||||
lazyConnEnabled bool
|
lazyConnEnabled bool
|
||||||
profilesDisabled bool
|
profilesDisabled bool
|
||||||
|
updateSettingsDisabled bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird",
|
Use: "netbird",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
|
|
||||||
var serviceCmd = &cobra.Command{
|
var serviceCmd = &cobra.Command{
|
||||||
Use: "service",
|
Use: "service",
|
||||||
Short: "manages NetBird service",
|
Short: "Manage the NetBird daemon service",
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -42,12 +42,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd, svcStatusCmd, installCmd, uninstallCmd, reconfigureCmd)
|
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd, svcStatusCmd, installCmd, uninstallCmd, reconfigureCmd)
|
||||||
serviceCmd.PersistentFlags().BoolVar(&profilesDisabled, "disable-profiles", false, "Disables profiles feature. If enabled, the client will not be able to change or edit any profile.")
|
serviceCmd.PersistentFlags().BoolVar(&profilesDisabled, "disable-profiles", false, "Disables profiles feature. If enabled, the client will not be able to change or edit any profile. To persist this setting, use: netbird service install --disable-profiles")
|
||||||
|
serviceCmd.PersistentFlags().BoolVar(&updateSettingsDisabled, "disable-update-settings", false, "Disables update settings feature. If enabled, the client will not be able to change or edit any settings. To persist this setting, use: netbird service install --disable-update-settings")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
|
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
|
||||||
serviceEnvDesc := `Sets extra environment variables for the service. ` +
|
serviceEnvDesc := `Sets extra environment variables for the service. ` +
|
||||||
`You can specify a comma-separated list of KEY=VALUE pairs. ` +
|
`You can specify a comma-separated list of KEY=VALUE pairs. ` +
|
||||||
`E.g. --service-env LOG_LEVEL=debug,CUSTOM_VAR=value`
|
`E.g. --service-env NB_LOG_LEVEL=debug,CUSTOM_VAR=value`
|
||||||
|
|
||||||
installCmd.Flags().StringSliceVar(&serviceEnvVars, "service-env", nil, serviceEnvDesc)
|
installCmd.Flags().StringSliceVar(&serviceEnvVars, "service-env", nil, serviceEnvDesc)
|
||||||
reconfigureCmd.Flags().StringSliceVar(&serviceEnvVars, "service-env", nil, serviceEnvDesc)
|
reconfigureCmd.Flags().StringSliceVar(&serviceEnvVars, "service-env", nil, serviceEnvDesc)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (p *program) Start(svc service.Service) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), configPath, profilesDisabled)
|
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), configPath, profilesDisabled, updateSettingsDisabled)
|
||||||
if err := serverInstance.Start(); err != nil {
|
if err := serverInstance.Start(); err != nil {
|
||||||
log.Fatalf("failed to start daemon: %v", err)
|
log.Fatalf("failed to start daemon: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ func buildServiceArguments() []string {
|
|||||||
args = append(args, "--log-file", logFile)
|
args = append(args, "--log-file", logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if profilesDisabled {
|
||||||
|
args = append(args, "--disable-profiles")
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateSettingsDisabled {
|
||||||
|
args = append(args, "--disable-update-settings")
|
||||||
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +107,7 @@ func createServiceConfigForInstall() (*service.Config, error) {
|
|||||||
|
|
||||||
var installCmd = &cobra.Command{
|
var installCmd = &cobra.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
Short: "installs NetBird service",
|
Short: "Install NetBird service",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := setupServiceCommand(cmd); err != nil {
|
if err := setupServiceCommand(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ var sshCmd = &cobra.Command{
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Short: "connect to a remote SSH server",
|
Short: "Connect to a remote SSH server",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
SetFlagsFromEnvVars(rootCmd)
|
SetFlagsFromEnvVars(rootCmd)
|
||||||
SetFlagsFromEnvVars(cmd)
|
SetFlagsFromEnvVars(cmd)
|
||||||
@@ -59,8 +59,8 @@ var sshCmd = &cobra.Command{
|
|||||||
|
|
||||||
ctx := internal.CtxInitState(cmd.Context())
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
|
||||||
pm := profilemanager.NewProfileManager()
|
sm := profilemanager.NewServiceManager(configPath)
|
||||||
activeProf, err := pm.GetActiveProfile()
|
activeProf, err := sm.GetActiveProfileState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get active profile: %v", err)
|
return fmt.Errorf("get active profile: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ var (
|
|||||||
|
|
||||||
var statusCmd = &cobra.Command{
|
var statusCmd = &cobra.Command{
|
||||||
Use: "status",
|
Use: "status",
|
||||||
Short: "status of the Netbird Service",
|
Short: "Display NetBird client status",
|
||||||
|
Long: "Display the current status of the NetBird client, including connection status, peer information, and network details.",
|
||||||
RunE: statusFunc,
|
RunE: statusFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,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:
|
||||||
|
|||||||
@@ -10,8 +10,15 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
|
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||||
|
client "github.com/netbirdio/netbird/client/server"
|
||||||
|
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||||
"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/integrations/port_forwarding"
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
|
"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"
|
||||||
@@ -26,15 +33,15 @@ import (
|
|||||||
|
|
||||||
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"
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
|
||||||
mgmt "github.com/netbirdio/netbird/management/server"
|
mgmt "github.com/netbirdio/netbird/management/server"
|
||||||
|
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
sigProto "github.com/netbirdio/netbird/shared/signal/proto"
|
sigProto "github.com/netbirdio/netbird/shared/signal/proto"
|
||||||
sig "github.com/netbirdio/netbird/signal/server"
|
sig "github.com/netbirdio/netbird/signal/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startTestingServices(t *testing.T) string {
|
func startTestingServices(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
config := &types.Config{}
|
config := &config.Config{}
|
||||||
_, err := util.ReadJson("../testdata/management.json", config)
|
_, err := util.ReadJson("../testdata/management.json", config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -69,7 +76,7 @@ func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
|
|||||||
return s, lis
|
return s, lis
|
||||||
}
|
}
|
||||||
|
|
||||||
func startManagement(t *testing.T, config *types.Config, testFile string) (*grpc.Server, net.Listener) {
|
func startManagement(t *testing.T, config *config.Config, testFile string) (*grpc.Server, net.Listener) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", ":0")
|
lis, err := net.Listen("tcp", ":0")
|
||||||
@@ -84,6 +91,7 @@ func startManagement(t *testing.T, config *types.Config, testFile string) (*grpc
|
|||||||
t.Cleanup(cleanUp)
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||||
|
jobManager := mgmt.NewJobManager(nil, store)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -97,19 +105,20 @@ func startManagement(t *testing.T, config *types.Config, testFile string) (*grpc
|
|||||||
|
|
||||||
settingsMockManager := settings.NewMockManager(ctrl)
|
settingsMockManager := settings.NewMockManager(ctrl)
|
||||||
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
||||||
|
groupsManager := groups.NewManagerMock()
|
||||||
|
|
||||||
settingsMockManager.EXPECT().
|
settingsMockManager.EXPECT().
|
||||||
GetSettings(gomock.Any(), gomock.Any(), gomock.Any()).
|
GetSettings(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
Return(&types.Settings{}, nil).
|
Return(&types.Settings{}, nil).
|
||||||
AnyTimes()
|
AnyTimes()
|
||||||
|
|
||||||
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, jobManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
|
secretsManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
||||||
mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil, &mgmt.MockIntegratedValidator{})
|
mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, jobManager, secretsManager, nil, &manager.EphemeralManager{}, nil, &mgmt.MockIntegratedValidator{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -134,7 +143,7 @@ func startClientDaemon(
|
|||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
|
|
||||||
server := client.New(ctx,
|
server := client.New(ctx,
|
||||||
"", "", false)
|
"", "", false, false)
|
||||||
if err := server.Start(); err != nil {
|
if err := server.Start(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ var (
|
|||||||
|
|
||||||
upCmd = &cobra.Command{
|
upCmd = &cobra.Command{
|
||||||
Use: "up",
|
Use: "up",
|
||||||
Short: "install, login and start NetBird client",
|
Short: "Connect to the NetBird network",
|
||||||
|
Long: "Connect to the NetBird network using the provided setup key or SSO auth. This command will bring up the WireGuard interface, connect to the management server, and establish peer-to-peer connections with other peers in the network if required.",
|
||||||
RunE: upFunc,
|
RunE: upFunc,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -195,7 +196,8 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command, activeProf *pr
|
|||||||
r := peer.NewRecorder(config.ManagementURL.String())
|
r := peer.NewRecorder(config.ManagementURL.String())
|
||||||
r.GetFullStatus()
|
r.GetFullStatus()
|
||||||
|
|
||||||
connectClient := internal.NewConnectClient(ctx, config, r)
|
//todo: do we need to pass logFile here ?
|
||||||
|
connectClient := internal.NewConnectClient(ctx, config, r, "")
|
||||||
SetupDebugHandler(ctx, config, r, connectClient, "")
|
SetupDebugHandler(ctx, config, r, connectClient, "")
|
||||||
|
|
||||||
return connectClient.Run(nil)
|
return connectClient.Run(nil)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
versionCmd = &cobra.Command{
|
versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "prints NetBird version",
|
Short: "Print the NetBird's client application version",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
cmd.Println(version.NetbirdVersion())
|
cmd.Println(version.NetbirdVersion())
|
||||||
|
|||||||
@@ -23,23 +23,29 @@ import (
|
|||||||
|
|
||||||
var ErrClientAlreadyStarted = errors.New("client already started")
|
var ErrClientAlreadyStarted = errors.New("client already started")
|
||||||
var ErrClientNotStarted = errors.New("client not started")
|
var ErrClientNotStarted = errors.New("client not started")
|
||||||
|
var ErrConfigNotInitialized = errors.New("config not initialized")
|
||||||
|
|
||||||
// Client manages a netbird embedded client instance
|
// Client manages a netbird embedded client instance.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
deviceName string
|
deviceName string
|
||||||
config *profilemanager.Config
|
config *profilemanager.Config
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
setupKey string
|
setupKey string
|
||||||
|
jwtToken string
|
||||||
connect *internal.ConnectClient
|
connect *internal.ConnectClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options configures a new Client
|
// Options configures a new Client.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// DeviceName is this peer's name in the network
|
// DeviceName is this peer's name in the network
|
||||||
DeviceName string
|
DeviceName string
|
||||||
// SetupKey is used for authentication
|
// SetupKey is used for authentication
|
||||||
SetupKey string
|
SetupKey string
|
||||||
|
// JWTToken is used for JWT-based authentication
|
||||||
|
JWTToken string
|
||||||
|
// PrivateKey is used for direct private key authentication
|
||||||
|
PrivateKey string
|
||||||
// ManagementURL overrides the default management server URL
|
// ManagementURL overrides the default management server URL
|
||||||
ManagementURL string
|
ManagementURL string
|
||||||
// PreSharedKey is the pre-shared key for the WireGuard interface
|
// PreSharedKey is the pre-shared key for the WireGuard interface
|
||||||
@@ -58,8 +64,35 @@ type Options struct {
|
|||||||
DisableClientRoutes bool
|
DisableClientRoutes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new netbird embedded client
|
// validateCredentials checks that exactly one credential type is provided
|
||||||
|
func (opts *Options) validateCredentials() error {
|
||||||
|
credentialsProvided := 0
|
||||||
|
if opts.SetupKey != "" {
|
||||||
|
credentialsProvided++
|
||||||
|
}
|
||||||
|
if opts.JWTToken != "" {
|
||||||
|
credentialsProvided++
|
||||||
|
}
|
||||||
|
if opts.PrivateKey != "" {
|
||||||
|
credentialsProvided++
|
||||||
|
}
|
||||||
|
|
||||||
|
if credentialsProvided == 0 {
|
||||||
|
return fmt.Errorf("one of SetupKey, JWTToken, or PrivateKey must be provided")
|
||||||
|
}
|
||||||
|
if credentialsProvided > 1 {
|
||||||
|
return fmt.Errorf("only one of SetupKey, JWTToken, or PrivateKey can be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new netbird embedded client.
|
||||||
func New(opts Options) (*Client, error) {
|
func New(opts Options) (*Client, error) {
|
||||||
|
if err := opts.validateCredentials(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if opts.LogOutput != nil {
|
if opts.LogOutput != nil {
|
||||||
logrus.SetOutput(opts.LogOutput)
|
logrus.SetOutput(opts.LogOutput)
|
||||||
}
|
}
|
||||||
@@ -107,9 +140,14 @@ func New(opts Options) (*Client, error) {
|
|||||||
return nil, fmt.Errorf("create config: %w", err)
|
return nil, fmt.Errorf("create config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.PrivateKey != "" {
|
||||||
|
config.PrivateKey = opts.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
deviceName: opts.DeviceName,
|
deviceName: opts.DeviceName,
|
||||||
setupKey: opts.SetupKey,
|
setupKey: opts.SetupKey,
|
||||||
|
jwtToken: opts.JWTToken,
|
||||||
config: config,
|
config: config,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -126,12 +164,14 @@ func (c *Client) Start(startCtx context.Context) error {
|
|||||||
ctx := internal.CtxInitState(context.Background())
|
ctx := internal.CtxInitState(context.Background())
|
||||||
// nolint:staticcheck
|
// nolint:staticcheck
|
||||||
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, c.deviceName)
|
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, c.deviceName)
|
||||||
if err := internal.Login(ctx, c.config, c.setupKey, ""); err != nil {
|
if err := internal.Login(ctx, c.config, c.setupKey, c.jwtToken); err != nil {
|
||||||
return fmt.Errorf("login: %w", err)
|
return fmt.Errorf("login: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder := peer.NewRecorder(c.config.ManagementURL.String())
|
recorder := peer.NewRecorder(c.config.ManagementURL.String())
|
||||||
client := internal.NewConnectClient(ctx, c.config, recorder)
|
|
||||||
|
//todo: do we need to pass logFile here ?
|
||||||
|
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)
|
||||||
// TODO: make after-startup backoff err available
|
// TODO: make after-startup backoff err available
|
||||||
@@ -187,6 +227,16 @@ func (c *Client) Stop(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfig returns a copy of the internal client config.
|
||||||
|
func (c *Client) GetConfig() (profilemanager.Config, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.config == nil {
|
||||||
|
return profilemanager.Config{}, ErrConfigNotInitialized
|
||||||
|
}
|
||||||
|
return *c.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Dial dials a network address in the netbird network.
|
// Dial dials a network address in the netbird network.
|
||||||
// Not applicable if the userspace networking mode is disabled.
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
func (c *Client) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
func (c *Client) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
@@ -211,7 +261,7 @@ func (c *Client) Dial(ctx context.Context, network, address string) (net.Conn, e
|
|||||||
return nsnet.DialContext(ctx, network, address)
|
return nsnet.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenTCP listens on the given address in the netbird network
|
// ListenTCP listens on the given address in the netbird network.
|
||||||
// Not applicable if the userspace networking mode is disabled.
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
func (c *Client) ListenTCP(address string) (net.Listener, error) {
|
func (c *Client) ListenTCP(address string) (net.Listener, error) {
|
||||||
nsnet, addr, err := c.getNet()
|
nsnet, addr, err := c.getNet()
|
||||||
@@ -232,7 +282,7 @@ func (c *Client) ListenTCP(address string) (net.Listener, error) {
|
|||||||
return nsnet.ListenTCP(tcpAddr)
|
return nsnet.ListenTCP(tcpAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenUDP listens on the given address in the netbird network
|
// ListenUDP listens on the given address in the netbird network.
|
||||||
// Not applicable if the userspace networking mode is disabled.
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
func (c *Client) ListenUDP(address string) (net.PacketConn, error) {
|
func (c *Client) ListenUDP(address string) (net.PacketConn, error) {
|
||||||
nsnet, addr, err := c.getNet()
|
nsnet, addr, err := c.getNet()
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func (m *aclManager) AddPeerFiltering(
|
|||||||
) ([]firewall.Rule, error) {
|
) ([]firewall.Rule, error) {
|
||||||
chain := chainNameInputRules
|
chain := chainNameInputRules
|
||||||
|
|
||||||
ipsetName = transformIPsetName(ipsetName, sPort, dPort)
|
ipsetName = transformIPsetName(ipsetName, sPort, dPort, action)
|
||||||
specs := filterRuleSpecs(ip, string(protocol), sPort, dPort, action, ipsetName)
|
specs := filterRuleSpecs(ip, string(protocol), sPort, dPort, action, ipsetName)
|
||||||
|
|
||||||
mangleSpecs := slices.Clone(specs)
|
mangleSpecs := slices.Clone(specs)
|
||||||
@@ -135,7 +135,14 @@ func (m *aclManager) AddPeerFiltering(
|
|||||||
return nil, fmt.Errorf("rule already exists")
|
return nil, fmt.Errorf("rule already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.iptablesClient.Append(tableFilter, chain, specs...); err != nil {
|
// Insert DROP rules at the beginning, append ACCEPT rules at the end
|
||||||
|
if action == firewall.ActionDrop {
|
||||||
|
// Insert at the beginning of the chain (position 1)
|
||||||
|
err = m.iptablesClient.Insert(tableFilter, chain, 1, specs...)
|
||||||
|
} else {
|
||||||
|
err = m.iptablesClient.Append(tableFilter, chain, specs...)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,17 +395,25 @@ func actionToStr(action firewall.Action) string {
|
|||||||
return "DROP"
|
return "DROP"
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformIPsetName(ipsetName string, sPort, dPort *firewall.Port) string {
|
func transformIPsetName(ipsetName string, sPort, dPort *firewall.Port, action firewall.Action) string {
|
||||||
switch {
|
if ipsetName == "" {
|
||||||
case ipsetName == "":
|
|
||||||
return ""
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include action in the ipset name to prevent squashing rules with different actions
|
||||||
|
actionSuffix := ""
|
||||||
|
if action == firewall.ActionDrop {
|
||||||
|
actionSuffix = "-drop"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
case sPort != nil && dPort != nil:
|
case sPort != nil && dPort != nil:
|
||||||
return ipsetName + "-sport-dport"
|
return ipsetName + "-sport-dport" + actionSuffix
|
||||||
case sPort != nil:
|
case sPort != nil:
|
||||||
return ipsetName + "-sport"
|
return ipsetName + "-sport" + actionSuffix
|
||||||
case dPort != nil:
|
case dPort != nil:
|
||||||
return ipsetName + "-dport"
|
return ipsetName + "-dport" + actionSuffix
|
||||||
default:
|
default:
|
||||||
return ipsetName
|
return ipsetName + actionSuffix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package iptables
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
|
|
||||||
var ifaceMock = &iFaceMock{
|
var ifaceMock = &iFaceMock{
|
||||||
NameFunc: func() string {
|
NameFunc: func() string {
|
||||||
return "lo"
|
return "wg-test"
|
||||||
},
|
},
|
||||||
AddressFunc: func() wgaddr.Address {
|
AddressFunc: func() wgaddr.Address {
|
||||||
return wgaddr.Address{
|
return wgaddr.Address{
|
||||||
@@ -109,10 +110,84 @@ func TestIptablesManager(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIptablesManagerDenyRules(t *testing.T) {
|
||||||
|
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
manager, err := Create(ifaceMock)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, manager.Init(nil))
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := manager.Close(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Run("add deny rule", func(t *testing.T) {
|
||||||
|
ip := netip.MustParseAddr("10.20.0.3")
|
||||||
|
port := &fw.Port{Values: []uint16{22}}
|
||||||
|
|
||||||
|
rule, err := manager.AddPeerFiltering(nil, ip.AsSlice(), "tcp", nil, port, fw.ActionDrop, "deny-ssh")
|
||||||
|
require.NoError(t, err, "failed to add deny rule")
|
||||||
|
require.NotEmpty(t, rule, "deny rule should not be empty")
|
||||||
|
|
||||||
|
// Verify the rule was added by checking iptables
|
||||||
|
for _, r := range rule {
|
||||||
|
rr := r.(*Rule)
|
||||||
|
checkRuleSpecs(t, ipv4Client, rr.chain, true, rr.specs...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("deny rule precedence test", func(t *testing.T) {
|
||||||
|
ip := netip.MustParseAddr("10.20.0.4")
|
||||||
|
port := &fw.Port{Values: []uint16{80}}
|
||||||
|
|
||||||
|
// Add accept rule first
|
||||||
|
_, err := manager.AddPeerFiltering(nil, ip.AsSlice(), "tcp", nil, port, fw.ActionAccept, "accept-http")
|
||||||
|
require.NoError(t, err, "failed to add accept rule")
|
||||||
|
|
||||||
|
// Add deny rule second for same IP/port - this should take precedence
|
||||||
|
_, err = manager.AddPeerFiltering(nil, ip.AsSlice(), "tcp", nil, port, fw.ActionDrop, "deny-http")
|
||||||
|
require.NoError(t, err, "failed to add deny rule")
|
||||||
|
|
||||||
|
// Inspect the actual iptables rules to verify deny rule comes before accept rule
|
||||||
|
rules, err := ipv4Client.List("filter", chainNameInputRules)
|
||||||
|
require.NoError(t, err, "failed to list iptables rules")
|
||||||
|
|
||||||
|
// Debug: print all rules
|
||||||
|
t.Logf("All iptables rules in chain %s:", chainNameInputRules)
|
||||||
|
for i, rule := range rules {
|
||||||
|
t.Logf(" [%d] %s", i, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
var denyRuleIndex, acceptRuleIndex int = -1, -1
|
||||||
|
for i, rule := range rules {
|
||||||
|
if strings.Contains(rule, "DROP") {
|
||||||
|
t.Logf("Found DROP rule at index %d: %s", i, rule)
|
||||||
|
if strings.Contains(rule, "deny-http") && strings.Contains(rule, "80") {
|
||||||
|
denyRuleIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(rule, "ACCEPT") {
|
||||||
|
t.Logf("Found ACCEPT rule at index %d: %s", i, rule)
|
||||||
|
if strings.Contains(rule, "accept-http") && strings.Contains(rule, "80") {
|
||||||
|
acceptRuleIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotEqual(t, -1, denyRuleIndex, "deny rule should exist in iptables")
|
||||||
|
require.NotEqual(t, -1, acceptRuleIndex, "accept rule should exist in iptables")
|
||||||
|
require.Less(t, denyRuleIndex, acceptRuleIndex,
|
||||||
|
"deny rule should come before accept rule in iptables chain (deny at index %d, accept at index %d)",
|
||||||
|
denyRuleIndex, acceptRuleIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestIptablesManagerIPSet(t *testing.T) {
|
func TestIptablesManagerIPSet(t *testing.T) {
|
||||||
mock := &iFaceMock{
|
mock := &iFaceMock{
|
||||||
NameFunc: func() string {
|
NameFunc: func() string {
|
||||||
return "lo"
|
return "wg-test"
|
||||||
},
|
},
|
||||||
AddressFunc: func() wgaddr.Address {
|
AddressFunc: func() wgaddr.Address {
|
||||||
return wgaddr.Address{
|
return wgaddr.Address{
|
||||||
@@ -176,7 +251,7 @@ func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName strin
|
|||||||
func TestIptablesCreatePerformance(t *testing.T) {
|
func TestIptablesCreatePerformance(t *testing.T) {
|
||||||
mock := &iFaceMock{
|
mock := &iFaceMock{
|
||||||
NameFunc: func() string {
|
NameFunc: func() string {
|
||||||
return "lo"
|
return "wg-test"
|
||||||
},
|
},
|
||||||
AddressFunc: func() wgaddr.Address {
|
AddressFunc: func() wgaddr.Address {
|
||||||
return wgaddr.Address{
|
return wgaddr.Address{
|
||||||
|
|||||||
@@ -341,30 +341,38 @@ func (m *AclManager) addIOFiltering(
|
|||||||
userData := []byte(ruleId)
|
userData := []byte(ruleId)
|
||||||
|
|
||||||
chain := m.chainInputRules
|
chain := m.chainInputRules
|
||||||
nftRule := m.rConn.AddRule(&nftables.Rule{
|
rule := &nftables.Rule{
|
||||||
Table: m.workTable,
|
Table: m.workTable,
|
||||||
Chain: chain,
|
Chain: chain,
|
||||||
Exprs: mainExpressions,
|
Exprs: mainExpressions,
|
||||||
UserData: userData,
|
UserData: userData,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Insert DROP rules at the beginning, append ACCEPT rules at the end
|
||||||
|
var nftRule *nftables.Rule
|
||||||
|
if action == firewall.ActionDrop {
|
||||||
|
nftRule = m.rConn.InsertRule(rule)
|
||||||
|
} else {
|
||||||
|
nftRule = m.rConn.AddRule(rule)
|
||||||
|
}
|
||||||
|
|
||||||
if err := m.rConn.Flush(); err != nil {
|
if err := m.rConn.Flush(); err != nil {
|
||||||
return nil, fmt.Errorf(flushError, err)
|
return nil, fmt.Errorf(flushError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule := &Rule{
|
ruleStruct := &Rule{
|
||||||
nftRule: nftRule,
|
nftRule: nftRule,
|
||||||
mangleRule: m.createPreroutingRule(expressions, userData),
|
mangleRule: m.createPreroutingRule(expressions, userData),
|
||||||
nftSet: ipset,
|
nftSet: ipset,
|
||||||
ruleID: ruleId,
|
ruleID: ruleId,
|
||||||
ip: ip,
|
ip: ip,
|
||||||
}
|
}
|
||||||
m.rules[ruleId] = rule
|
m.rules[ruleId] = ruleStruct
|
||||||
if ipset != nil {
|
if ipset != nil {
|
||||||
m.ipsetStore.AddReferenceToIpset(ipset.Name)
|
m.ipsetStore.AddReferenceToIpset(ipset.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rule, nil
|
return ruleStruct, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *AclManager) createPreroutingRule(expressions []expr.Any, userData []byte) *nftables.Rule {
|
func (m *AclManager) createPreroutingRule(expressions []expr.Any, userData []byte) *nftables.Rule {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package nftables
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -20,7 +21,7 @@ import (
|
|||||||
|
|
||||||
var ifaceMock = &iFaceMock{
|
var ifaceMock = &iFaceMock{
|
||||||
NameFunc: func() string {
|
NameFunc: func() string {
|
||||||
return "lo"
|
return "wg-test"
|
||||||
},
|
},
|
||||||
AddressFunc: func() wgaddr.Address {
|
AddressFunc: func() wgaddr.Address {
|
||||||
return wgaddr.Address{
|
return wgaddr.Address{
|
||||||
@@ -103,9 +104,8 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
Kind: expr.VerdictAccept,
|
Kind: expr.VerdictAccept,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
compareExprsIgnoringCounters(t, rules[0].Exprs, expectedExprs1)
|
// Since DROP rules are inserted at position 0, the DROP rule comes first
|
||||||
|
expectedDropExprs := []expr.Any{
|
||||||
expectedExprs2 := []expr.Any{
|
|
||||||
&expr.Payload{
|
&expr.Payload{
|
||||||
DestRegister: 1,
|
DestRegister: 1,
|
||||||
Base: expr.PayloadBaseNetworkHeader,
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
@@ -141,7 +141,12 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
},
|
},
|
||||||
&expr.Verdict{Kind: expr.VerdictDrop},
|
&expr.Verdict{Kind: expr.VerdictDrop},
|
||||||
}
|
}
|
||||||
require.ElementsMatch(t, rules[1].Exprs, expectedExprs2, "expected the same expressions")
|
|
||||||
|
// Compare DROP rule at position 0 (inserted first due to InsertRule)
|
||||||
|
compareExprsIgnoringCounters(t, rules[0].Exprs, expectedDropExprs)
|
||||||
|
|
||||||
|
// Compare connection tracking rule at position 1 (pushed down by DROP rule insertion)
|
||||||
|
compareExprsIgnoringCounters(t, rules[1].Exprs, expectedExprs1)
|
||||||
|
|
||||||
for _, r := range rule {
|
for _, r := range rule {
|
||||||
err = manager.DeletePeerRule(r)
|
err = manager.DeletePeerRule(r)
|
||||||
@@ -160,10 +165,90 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
require.NoError(t, err, "failed to reset")
|
require.NoError(t, err, "failed to reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNftablesManagerRuleOrder(t *testing.T) {
|
||||||
|
// This test verifies rule insertion order in nftables peer ACLs
|
||||||
|
// We add accept rule first, then deny rule to test ordering behavior
|
||||||
|
manager, err := Create(ifaceMock)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, manager.Init(nil))
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = manager.Close(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ip := netip.MustParseAddr("100.96.0.2").Unmap()
|
||||||
|
testClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
// Add accept rule first
|
||||||
|
_, err = manager.AddPeerFiltering(nil, ip.AsSlice(), fw.ProtocolTCP, nil, &fw.Port{Values: []uint16{80}}, fw.ActionAccept, "accept-http")
|
||||||
|
require.NoError(t, err, "failed to add accept rule")
|
||||||
|
|
||||||
|
// Add deny rule second for the same traffic
|
||||||
|
_, err = manager.AddPeerFiltering(nil, ip.AsSlice(), fw.ProtocolTCP, nil, &fw.Port{Values: []uint16{80}}, fw.ActionDrop, "deny-http")
|
||||||
|
require.NoError(t, err, "failed to add deny rule")
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err := testClient.GetRules(manager.aclManager.workTable, manager.aclManager.chainInputRules)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
|
||||||
|
t.Logf("Found %d rules in nftables chain", len(rules))
|
||||||
|
|
||||||
|
// Find the accept and deny rules and verify deny comes before accept
|
||||||
|
var acceptRuleIndex, denyRuleIndex int = -1, -1
|
||||||
|
for i, rule := range rules {
|
||||||
|
hasAcceptHTTPSet := false
|
||||||
|
hasDenyHTTPSet := false
|
||||||
|
hasPort80 := false
|
||||||
|
var action string
|
||||||
|
|
||||||
|
for _, e := range rule.Exprs {
|
||||||
|
// Check for set lookup
|
||||||
|
if lookup, ok := e.(*expr.Lookup); ok {
|
||||||
|
if lookup.SetName == "accept-http" {
|
||||||
|
hasAcceptHTTPSet = true
|
||||||
|
} else if lookup.SetName == "deny-http" {
|
||||||
|
hasDenyHTTPSet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for port 80
|
||||||
|
if cmp, ok := e.(*expr.Cmp); ok {
|
||||||
|
if cmp.Op == expr.CmpOpEq && len(cmp.Data) == 2 && binary.BigEndian.Uint16(cmp.Data) == 80 {
|
||||||
|
hasPort80 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for verdict
|
||||||
|
if verdict, ok := e.(*expr.Verdict); ok {
|
||||||
|
if verdict.Kind == expr.VerdictAccept {
|
||||||
|
action = "ACCEPT"
|
||||||
|
} else if verdict.Kind == expr.VerdictDrop {
|
||||||
|
action = "DROP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasAcceptHTTPSet && hasPort80 && action == "ACCEPT" {
|
||||||
|
t.Logf("Rule [%d]: accept-http set + Port 80 + ACCEPT", i)
|
||||||
|
acceptRuleIndex = i
|
||||||
|
} else if hasDenyHTTPSet && hasPort80 && action == "DROP" {
|
||||||
|
t.Logf("Rule [%d]: deny-http set + Port 80 + DROP", i)
|
||||||
|
denyRuleIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotEqual(t, -1, acceptRuleIndex, "accept rule should exist in nftables")
|
||||||
|
require.NotEqual(t, -1, denyRuleIndex, "deny rule should exist in nftables")
|
||||||
|
require.Less(t, denyRuleIndex, acceptRuleIndex,
|
||||||
|
"deny rule should come before accept rule in nftables chain (deny at index %d, accept at index %d)",
|
||||||
|
denyRuleIndex, acceptRuleIndex)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNFtablesCreatePerformance(t *testing.T) {
|
func TestNFtablesCreatePerformance(t *testing.T) {
|
||||||
mock := &iFaceMock{
|
mock := &iFaceMock{
|
||||||
NameFunc: func() string {
|
NameFunc: func() string {
|
||||||
return "lo"
|
return "wg-test"
|
||||||
},
|
},
|
||||||
AddressFunc: func() wgaddr.Address {
|
AddressFunc: func() wgaddr.Address {
|
||||||
return wgaddr.Address{
|
return wgaddr.Address{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ func (m *Manager) Close(stateManager *statemanager.Manager) error {
|
|||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
m.outgoingRules = make(map[netip.Addr]RuleSet)
|
m.outgoingRules = make(map[netip.Addr]RuleSet)
|
||||||
|
m.incomingDenyRules = make(map[netip.Addr]RuleSet)
|
||||||
m.incomingRules = make(map[netip.Addr]RuleSet)
|
m.incomingRules = make(map[netip.Addr]RuleSet)
|
||||||
|
|
||||||
if m.udpTracker != nil {
|
if m.udpTracker != nil {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func (m *Manager) Close(*statemanager.Manager) error {
|
|||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
m.outgoingRules = make(map[netip.Addr]RuleSet)
|
m.outgoingRules = make(map[netip.Addr]RuleSet)
|
||||||
|
m.incomingDenyRules = make(map[netip.Addr]RuleSet)
|
||||||
m.incomingRules = make(map[netip.Addr]RuleSet)
|
m.incomingRules = make(map[netip.Addr]RuleSet)
|
||||||
|
|
||||||
if m.udpTracker != nil {
|
if m.udpTracker != nil {
|
||||||
|
|||||||
@@ -70,14 +70,13 @@ func (r RouteRules) Sort() {
|
|||||||
|
|
||||||
// Manager userspace firewall manager
|
// Manager userspace firewall manager
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
// outgoingRules is used for hooks only
|
outgoingRules map[netip.Addr]RuleSet
|
||||||
outgoingRules map[netip.Addr]RuleSet
|
incomingDenyRules map[netip.Addr]RuleSet
|
||||||
// incomingRules is used for filtering and hooks
|
incomingRules map[netip.Addr]RuleSet
|
||||||
incomingRules map[netip.Addr]RuleSet
|
routeRules RouteRules
|
||||||
routeRules RouteRules
|
decoders sync.Pool
|
||||||
decoders sync.Pool
|
wgIface common.IFaceMapper
|
||||||
wgIface common.IFaceMapper
|
nativeFirewall firewall.Manager
|
||||||
nativeFirewall firewall.Manager
|
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
|
||||||
@@ -186,6 +185,7 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
|
|||||||
},
|
},
|
||||||
nativeFirewall: nativeFirewall,
|
nativeFirewall: nativeFirewall,
|
||||||
outgoingRules: make(map[netip.Addr]RuleSet),
|
outgoingRules: make(map[netip.Addr]RuleSet),
|
||||||
|
incomingDenyRules: make(map[netip.Addr]RuleSet),
|
||||||
incomingRules: make(map[netip.Addr]RuleSet),
|
incomingRules: make(map[netip.Addr]RuleSet),
|
||||||
wgIface: iface,
|
wgIface: iface,
|
||||||
localipmanager: newLocalIPManager(),
|
localipmanager: newLocalIPManager(),
|
||||||
@@ -417,10 +417,17 @@ func (m *Manager) AddPeerFiltering(
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
if _, ok := m.incomingRules[r.ip]; !ok {
|
var targetMap map[netip.Addr]RuleSet
|
||||||
m.incomingRules[r.ip] = make(RuleSet)
|
if r.drop {
|
||||||
|
targetMap = m.incomingDenyRules
|
||||||
|
} else {
|
||||||
|
targetMap = m.incomingRules
|
||||||
}
|
}
|
||||||
m.incomingRules[r.ip][r.id] = r
|
|
||||||
|
if _, ok := targetMap[r.ip]; !ok {
|
||||||
|
targetMap[r.ip] = make(RuleSet)
|
||||||
|
}
|
||||||
|
targetMap[r.ip][r.id] = r
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
return []firewall.Rule{&r}, nil
|
return []firewall.Rule{&r}, nil
|
||||||
}
|
}
|
||||||
@@ -507,10 +514,24 @@ func (m *Manager) DeletePeerRule(rule firewall.Rule) error {
|
|||||||
return fmt.Errorf("delete rule: invalid rule type: %T", rule)
|
return fmt.Errorf("delete rule: invalid rule type: %T", rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := m.incomingRules[r.ip][r.id]; !ok {
|
var sourceMap map[netip.Addr]RuleSet
|
||||||
|
if r.drop {
|
||||||
|
sourceMap = m.incomingDenyRules
|
||||||
|
} else {
|
||||||
|
sourceMap = m.incomingRules
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleset, ok := sourceMap[r.ip]; ok {
|
||||||
|
if _, exists := ruleset[r.id]; !exists {
|
||||||
|
return fmt.Errorf("delete rule: no rule with such id: %v", r.id)
|
||||||
|
}
|
||||||
|
delete(ruleset, r.id)
|
||||||
|
if len(ruleset) == 0 {
|
||||||
|
delete(sourceMap, r.ip)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return fmt.Errorf("delete rule: no rule with such id: %v", r.id)
|
return fmt.Errorf("delete rule: no rule with such id: %v", r.id)
|
||||||
}
|
}
|
||||||
delete(m.incomingRules[r.ip], r.id)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -572,7 +593,7 @@ func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterOutBound filters outgoing packets
|
// FilterOutbound filters outgoing packets
|
||||||
func (m *Manager) FilterOutbound(packetData []byte, size int) bool {
|
func (m *Manager) FilterOutbound(packetData []byte, size int) bool {
|
||||||
return m.filterOutbound(packetData, size)
|
return m.filterOutbound(packetData, size)
|
||||||
}
|
}
|
||||||
@@ -761,7 +782,7 @@ func (m *Manager) filterInbound(packetData []byte, size int) bool {
|
|||||||
// handleLocalTraffic handles local traffic.
|
// handleLocalTraffic handles local traffic.
|
||||||
// If it returns true, the packet should be dropped.
|
// If it returns true, the packet should be dropped.
|
||||||
func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP netip.Addr, packetData []byte, size int) bool {
|
func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP netip.Addr, packetData []byte, size int) bool {
|
||||||
ruleID, blocked := m.peerACLsBlock(srcIP, packetData, m.incomingRules, d)
|
ruleID, blocked := m.peerACLsBlock(srcIP, d, packetData)
|
||||||
if blocked {
|
if blocked {
|
||||||
_, pnum := getProtocolFromPacket(d)
|
_, pnum := getProtocolFromPacket(d)
|
||||||
srcPort, dstPort := getPortsFromPacket(d)
|
srcPort, dstPort := getPortsFromPacket(d)
|
||||||
@@ -971,26 +992,28 @@ func (m *Manager) isSpecialICMP(d *decoder) bool {
|
|||||||
icmpType == layers.ICMPv4TypeTimeExceeded
|
icmpType == layers.ICMPv4TypeTimeExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) peerACLsBlock(srcIP netip.Addr, packetData []byte, rules map[netip.Addr]RuleSet, d *decoder) ([]byte, bool) {
|
func (m *Manager) peerACLsBlock(srcIP netip.Addr, d *decoder, packetData []byte) ([]byte, bool) {
|
||||||
m.mutex.RLock()
|
m.mutex.RLock()
|
||||||
defer m.mutex.RUnlock()
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
if m.isSpecialICMP(d) {
|
if m.isSpecialICMP(d) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[srcIP], d); ok {
|
if mgmtId, filter, ok := validateRule(srcIP, packetData, m.incomingDenyRules[srcIP], d); ok {
|
||||||
return mgmtId, filter
|
return mgmtId, filter
|
||||||
}
|
}
|
||||||
|
|
||||||
if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[netip.IPv4Unspecified()], d); ok {
|
if mgmtId, filter, ok := validateRule(srcIP, packetData, m.incomingRules[srcIP], d); ok {
|
||||||
|
return mgmtId, filter
|
||||||
|
}
|
||||||
|
if mgmtId, filter, ok := validateRule(srcIP, packetData, m.incomingRules[netip.IPv4Unspecified()], d); ok {
|
||||||
|
return mgmtId, filter
|
||||||
|
}
|
||||||
|
if mgmtId, filter, ok := validateRule(srcIP, packetData, m.incomingRules[netip.IPv6Unspecified()], d); ok {
|
||||||
return mgmtId, filter
|
return mgmtId, filter
|
||||||
}
|
}
|
||||||
|
|
||||||
if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[netip.IPv6Unspecified()], d); ok {
|
|
||||||
return mgmtId, filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default policy: DROP ALL
|
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,6 +1036,7 @@ func portsMatch(rulePort *firewall.Port, packetPort uint16) bool {
|
|||||||
|
|
||||||
func validateRule(ip netip.Addr, packetData []byte, rules map[string]PeerRule, d *decoder) ([]byte, bool, bool) {
|
func validateRule(ip netip.Addr, packetData []byte, rules map[string]PeerRule, d *decoder) ([]byte, bool, bool) {
|
||||||
payloadLayer := d.decoded[1]
|
payloadLayer := d.decoded[1]
|
||||||
|
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if rule.matchByIP && ip.Compare(rule.ip) != 0 {
|
if rule.matchByIP && ip.Compare(rule.ip) != 0 {
|
||||||
continue
|
continue
|
||||||
@@ -1045,6 +1069,7 @@ func validateRule(ip netip.Addr, packetData []byte, rules map[string]PeerRule, d
|
|||||||
return rule.mgmtId, rule.drop, true
|
return rule.mgmtId, rule.drop, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,6 +1141,7 @@ func (m *Manager) AddUDPPacketHook(in bool, ip netip.Addr, dPort uint16, hook fu
|
|||||||
|
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
if in {
|
if in {
|
||||||
|
// Incoming UDP hooks are stored in allow rules map
|
||||||
if _, ok := m.incomingRules[r.ip]; !ok {
|
if _, ok := m.incomingRules[r.ip]; !ok {
|
||||||
m.incomingRules[r.ip] = make(map[string]PeerRule)
|
m.incomingRules[r.ip] = make(map[string]PeerRule)
|
||||||
}
|
}
|
||||||
@@ -1136,6 +1162,7 @@ func (m *Manager) RemovePacketHook(hookID string) error {
|
|||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
// Check incoming hooks (stored in allow rules)
|
||||||
for _, arr := range m.incomingRules {
|
for _, arr := range m.incomingRules {
|
||||||
for _, r := range arr {
|
for _, r := range arr {
|
||||||
if r.id == hookID {
|
if r.id == hookID {
|
||||||
@@ -1144,6 +1171,7 @@ func (m *Manager) RemovePacketHook(hookID string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check outgoing hooks
|
||||||
for _, arr := range m.outgoingRules {
|
for _, arr := range m.outgoingRules {
|
||||||
for _, r := range arr {
|
for _, r := range arr {
|
||||||
if r.id == hookID {
|
if r.id == hookID {
|
||||||
|
|||||||
@@ -458,6 +458,31 @@ func TestPeerACLFiltering(t *testing.T) {
|
|||||||
ruleAction: fw.ActionDrop,
|
ruleAction: fw.ActionDrop,
|
||||||
shouldBeBlocked: true,
|
shouldBeBlocked: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Peer ACL - Drop rule should override accept all rule",
|
||||||
|
srcIP: "100.10.0.1",
|
||||||
|
dstIP: "100.10.0.100",
|
||||||
|
proto: fw.ProtocolTCP,
|
||||||
|
srcPort: 12345,
|
||||||
|
dstPort: 22,
|
||||||
|
ruleIP: "100.10.0.1",
|
||||||
|
ruleProto: fw.ProtocolTCP,
|
||||||
|
ruleDstPort: &fw.Port{Values: []uint16{22}},
|
||||||
|
ruleAction: fw.ActionDrop,
|
||||||
|
shouldBeBlocked: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer ACL - Drop all traffic from specific IP",
|
||||||
|
srcIP: "100.10.0.99",
|
||||||
|
dstIP: "100.10.0.100",
|
||||||
|
proto: fw.ProtocolTCP,
|
||||||
|
srcPort: 12345,
|
||||||
|
dstPort: 80,
|
||||||
|
ruleIP: "100.10.0.99",
|
||||||
|
ruleProto: fw.ProtocolALL,
|
||||||
|
ruleAction: fw.ActionDrop,
|
||||||
|
shouldBeBlocked: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Implicit DROP (no rules)", func(t *testing.T) {
|
t.Run("Implicit DROP (no rules)", func(t *testing.T) {
|
||||||
@@ -468,13 +493,11 @@ func TestPeerACLFiltering(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
if tc.ruleAction == fw.ActionDrop {
|
if tc.ruleAction == fw.ActionDrop {
|
||||||
// add general accept rule to test drop rule
|
// add general accept rule for the same IP to test drop rule precedence
|
||||||
// TODO: this only works because 0.0.0.0 is tested last, we need to implement order
|
|
||||||
rules, err := manager.AddPeerFiltering(
|
rules, err := manager.AddPeerFiltering(
|
||||||
nil,
|
nil,
|
||||||
net.ParseIP("0.0.0.0"),
|
net.ParseIP(tc.ruleIP),
|
||||||
fw.ProtocolALL,
|
fw.ProtocolALL,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
|||||||
@@ -136,9 +136,22 @@ func TestManagerDeleteRule(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check rules exist in appropriate maps
|
||||||
for _, r := range rule2 {
|
for _, r := range rule2 {
|
||||||
if _, ok := m.incomingRules[ip][r.ID()]; !ok {
|
peerRule, ok := r.(*PeerRule)
|
||||||
t.Errorf("rule2 is not in the incomingRules")
|
if !ok {
|
||||||
|
t.Errorf("rule should be a PeerRule")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if rule exists in deny or allow maps based on action
|
||||||
|
var found bool
|
||||||
|
if peerRule.drop {
|
||||||
|
_, found = m.incomingDenyRules[ip][r.ID()]
|
||||||
|
} else {
|
||||||
|
_, found = m.incomingRules[ip][r.ID()]
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("rule2 is not in the expected rules map")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,9 +163,22 @@ func TestManagerDeleteRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check rules are removed from appropriate maps
|
||||||
for _, r := range rule2 {
|
for _, r := range rule2 {
|
||||||
if _, ok := m.incomingRules[ip][r.ID()]; ok {
|
peerRule, ok := r.(*PeerRule)
|
||||||
t.Errorf("rule2 is not in the incomingRules")
|
if !ok {
|
||||||
|
t.Errorf("rule should be a PeerRule")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if rule is removed from deny or allow maps based on action
|
||||||
|
var found bool
|
||||||
|
if peerRule.drop {
|
||||||
|
_, found = m.incomingDenyRules[ip][r.ID()]
|
||||||
|
} else {
|
||||||
|
_, found = m.incomingRules[ip][r.ID()]
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
t.Errorf("rule2 should be removed from the rules map")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,16 +222,17 @@ func TestAddUDPPacketHook(t *testing.T) {
|
|||||||
|
|
||||||
var addedRule PeerRule
|
var addedRule PeerRule
|
||||||
if tt.in {
|
if tt.in {
|
||||||
|
// Incoming UDP hooks are stored in allow rules map
|
||||||
if len(manager.incomingRules[tt.ip]) != 1 {
|
if len(manager.incomingRules[tt.ip]) != 1 {
|
||||||
t.Errorf("expected 1 incoming rule, got %d", len(manager.incomingRules))
|
t.Errorf("expected 1 incoming rule, got %d", len(manager.incomingRules[tt.ip]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, rule := range manager.incomingRules[tt.ip] {
|
for _, rule := range manager.incomingRules[tt.ip] {
|
||||||
addedRule = rule
|
addedRule = rule
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(manager.outgoingRules) != 1 {
|
if len(manager.outgoingRules[tt.ip]) != 1 {
|
||||||
t.Errorf("expected 1 outgoing rule, got %d", len(manager.outgoingRules))
|
t.Errorf("expected 1 outgoing rule, got %d", len(manager.outgoingRules[tt.ip]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, rule := range manager.outgoingRules[tt.ip] {
|
for _, rule := range manager.outgoingRules[tt.ip] {
|
||||||
@@ -261,8 +288,8 @@ func TestManagerReset(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.outgoingRules) != 0 || len(m.incomingRules) != 0 {
|
if len(m.outgoingRules) != 0 || len(m.incomingRules) != 0 || len(m.incomingDenyRules) != 0 {
|
||||||
t.Errorf("rules is not empty")
|
t.Errorf("rules are not empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ func (m *Manager) buildConntrackStateMessage(d *decoder) string {
|
|||||||
func (m *Manager) handleLocalDelivery(trace *PacketTrace, packetData []byte, d *decoder, srcIP, dstIP netip.Addr) bool {
|
func (m *Manager) handleLocalDelivery(trace *PacketTrace, packetData []byte, d *decoder, srcIP, dstIP netip.Addr) bool {
|
||||||
trace.AddResult(StageRouting, "Packet destined for local delivery", true)
|
trace.AddResult(StageRouting, "Packet destined for local delivery", true)
|
||||||
|
|
||||||
ruleId, blocked := m.peerACLsBlock(srcIP, packetData, m.incomingRules, d)
|
ruleId, blocked := m.peerACLsBlock(srcIP, d, packetData)
|
||||||
|
|
||||||
strRuleId := "<no id>"
|
strRuleId := "<no id>"
|
||||||
if ruleId != nil {
|
if ruleId != nil {
|
||||||
|
|||||||
7
client/iface/bind/error.go
Normal file
7
client/iface/bind/error.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUDPMUXNotSupported = fmt.Errorf("UDPMUX is not supported in WASM")
|
||||||
|
)
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
//go:build !js
|
||||||
|
|
||||||
package bind
|
package bind
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -8,22 +11,18 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/stun/v2"
|
"github.com/pion/stun/v3"
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
wgConn "golang.zx2c4.com/wireguard/conn"
|
wgConn "golang.zx2c4.com/wireguard/conn"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/client/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RecvMessage struct {
|
|
||||||
Endpoint *Endpoint
|
|
||||||
Buffer []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type receiverCreator struct {
|
type receiverCreator struct {
|
||||||
iceBind *ICEBind
|
iceBind *ICEBind
|
||||||
}
|
}
|
||||||
@@ -41,35 +40,38 @@ func (rc receiverCreator) CreateIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UD
|
|||||||
// use the port because in the Send function the wgConn.Endpoint the port info is not exported.
|
// use the port because in the Send function the wgConn.Endpoint the port info is not exported.
|
||||||
type ICEBind struct {
|
type ICEBind struct {
|
||||||
*wgConn.StdNetBind
|
*wgConn.StdNetBind
|
||||||
RecvChan chan RecvMessage
|
|
||||||
|
|
||||||
transportNet transport.Net
|
transportNet transport.Net
|
||||||
filterFn FilterFn
|
filterFn udpmux.FilterFn
|
||||||
endpoints map[netip.Addr]net.Conn
|
address wgaddr.Address
|
||||||
endpointsMu sync.Mutex
|
mtu uint16
|
||||||
|
|
||||||
|
endpoints map[netip.Addr]net.Conn
|
||||||
|
endpointsMu sync.Mutex
|
||||||
|
recvChan chan recvMessage
|
||||||
// every time when Close() is called (i.e. BindUpdate()) we need to close exit from the receiveRelayed and create a
|
// every time when Close() is called (i.e. BindUpdate()) we need to close exit from the receiveRelayed and create a
|
||||||
// new closed channel. With the closedChanMu we can safely close the channel and create a new one
|
// new closed channel. With the closedChanMu we can safely close the channel and create a new one
|
||||||
closedChan chan struct{}
|
closedChan chan struct{}
|
||||||
closedChanMu sync.RWMutex // protect the closeChan recreation from reading from it.
|
closedChanMu sync.RWMutex // protect the closeChan recreation from reading from it.
|
||||||
closed bool
|
closed bool
|
||||||
|
|
||||||
muUDPMux sync.Mutex
|
|
||||||
udpMux *UniversalUDPMuxDefault
|
|
||||||
address wgaddr.Address
|
|
||||||
activityRecorder *ActivityRecorder
|
activityRecorder *ActivityRecorder
|
||||||
|
|
||||||
|
muUDPMux sync.Mutex
|
||||||
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewICEBind(transportNet transport.Net, filterFn FilterFn, address wgaddr.Address) *ICEBind {
|
func NewICEBind(transportNet transport.Net, filterFn udpmux.FilterFn, address wgaddr.Address, mtu uint16) *ICEBind {
|
||||||
b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind)
|
b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind)
|
||||||
ib := &ICEBind{
|
ib := &ICEBind{
|
||||||
StdNetBind: b,
|
StdNetBind: b,
|
||||||
RecvChan: make(chan RecvMessage, 1),
|
|
||||||
transportNet: transportNet,
|
transportNet: transportNet,
|
||||||
filterFn: filterFn,
|
filterFn: filterFn,
|
||||||
|
address: address,
|
||||||
|
mtu: mtu,
|
||||||
endpoints: make(map[netip.Addr]net.Conn),
|
endpoints: make(map[netip.Addr]net.Conn),
|
||||||
|
recvChan: make(chan recvMessage, 1),
|
||||||
closedChan: make(chan struct{}),
|
closedChan: make(chan struct{}),
|
||||||
closed: true,
|
closed: true,
|
||||||
address: address,
|
|
||||||
activityRecorder: NewActivityRecorder(),
|
activityRecorder: NewActivityRecorder(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ func (s *ICEBind) ActivityRecorder() *ActivityRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
||||||
func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
|
func (s *ICEBind) GetICEMux() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
s.muUDPMux.Lock()
|
s.muUDPMux.Lock()
|
||||||
defer s.muUDPMux.Unlock()
|
defer s.muUDPMux.Unlock()
|
||||||
if s.udpMux == nil {
|
if s.udpMux == nil {
|
||||||
@@ -132,6 +134,16 @@ func (b *ICEBind) RemoveEndpoint(fakeIP netip.Addr) {
|
|||||||
delete(b.endpoints, fakeIP)
|
delete(b.endpoints, fakeIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *ICEBind) ReceiveFromEndpoint(ctx context.Context, ep *Endpoint, buf []byte) {
|
||||||
|
select {
|
||||||
|
case <-b.closedChan:
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case b.recvChan <- recvMessage{ep, buf}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error {
|
func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error {
|
||||||
b.endpointsMu.Lock()
|
b.endpointsMu.Lock()
|
||||||
conn, ok := b.endpoints[ep.DstIP()]
|
conn, ok := b.endpoints[ep.DstIP()]
|
||||||
@@ -152,12 +164,13 @@ func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, r
|
|||||||
s.muUDPMux.Lock()
|
s.muUDPMux.Lock()
|
||||||
defer s.muUDPMux.Unlock()
|
defer s.muUDPMux.Unlock()
|
||||||
|
|
||||||
s.udpMux = NewUniversalUDPMuxDefault(
|
s.udpMux = udpmux.NewUniversalUDPMuxDefault(
|
||||||
UniversalUDPMuxParams{
|
udpmux.UniversalUDPMuxParams{
|
||||||
UDPConn: nbnet.WrapPacketConn(conn),
|
UDPConn: nbnet.WrapPacketConn(conn),
|
||||||
Net: s.transportNet,
|
Net: s.transportNet,
|
||||||
FilterFn: s.filterFn,
|
FilterFn: s.filterFn,
|
||||||
WGAddress: s.address,
|
WGAddress: s.address,
|
||||||
|
MTU: s.mtu,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return func(bufs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) {
|
return func(bufs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) {
|
||||||
@@ -263,7 +276,7 @@ func (c *ICEBind) receiveRelayed(buffs [][]byte, sizes []int, eps []wgConn.Endpo
|
|||||||
select {
|
select {
|
||||||
case <-c.closedChan:
|
case <-c.closedChan:
|
||||||
return 0, net.ErrClosed
|
return 0, net.ErrClosed
|
||||||
case msg, ok := <-c.RecvChan:
|
case msg, ok := <-c.recvChan:
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, net.ErrClosed
|
return 0, net.ErrClosed
|
||||||
}
|
}
|
||||||
|
|||||||
6
client/iface/bind/recv_msg.go
Normal file
6
client/iface/bind/recv_msg.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
type recvMessage struct {
|
||||||
|
Endpoint *Endpoint
|
||||||
|
Buffer []byte
|
||||||
|
}
|
||||||
125
client/iface/bind/relay_bind.go
Normal file
125
client/iface/bind/relay_bind.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RelayBindJS is a conn.Bind implementation for WebAssembly environments.
|
||||||
|
// Do not limit to build only js, because we want to be able to run tests
|
||||||
|
type RelayBindJS struct {
|
||||||
|
*conn.StdNetBind
|
||||||
|
|
||||||
|
recvChan chan recvMessage
|
||||||
|
endpoints map[netip.Addr]net.Conn
|
||||||
|
endpointsMu sync.Mutex
|
||||||
|
activityRecorder *ActivityRecorder
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRelayBindJS() *RelayBindJS {
|
||||||
|
return &RelayBindJS{
|
||||||
|
recvChan: make(chan recvMessage, 100),
|
||||||
|
endpoints: make(map[netip.Addr]net.Conn),
|
||||||
|
activityRecorder: NewActivityRecorder(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates a receive function for handling relay packets in WASM.
|
||||||
|
func (s *RelayBindJS) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
|
||||||
|
log.Debugf("Open: creating receive function for port %d", uport)
|
||||||
|
|
||||||
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
receiveFn := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (int, error) {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
case msg, ok := <-s.recvChan:
|
||||||
|
if !ok {
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
}
|
||||||
|
copy(bufs[0], msg.Buffer)
|
||||||
|
sizes[0] = len(msg.Buffer)
|
||||||
|
eps[0] = conn.Endpoint(msg.Endpoint)
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Open: receive function created, returning port %d", uport)
|
||||||
|
return []conn.ReceiveFunc{receiveFn}, uport, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RelayBindJS) Close() error {
|
||||||
|
if s.cancel == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Debugf("close RelayBindJS")
|
||||||
|
s.cancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RelayBindJS) ReceiveFromEndpoint(ctx context.Context, ep *Endpoint, buf []byte) {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case s.recvChan <- recvMessage{ep, buf}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send forwards packets through the relay connection for WASM.
|
||||||
|
func (s *RelayBindJS) Send(bufs [][]byte, ep conn.Endpoint) error {
|
||||||
|
if ep == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeIP := ep.DstIP()
|
||||||
|
|
||||||
|
s.endpointsMu.Lock()
|
||||||
|
relayConn, ok := s.endpoints[fakeIP]
|
||||||
|
s.endpointsMu.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, buf := range bufs {
|
||||||
|
if _, err := relayConn.Write(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *RelayBindJS) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {
|
||||||
|
b.endpointsMu.Lock()
|
||||||
|
b.endpoints[fakeIP] = conn
|
||||||
|
b.endpointsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RelayBindJS) RemoveEndpoint(fakeIP netip.Addr) {
|
||||||
|
s.endpointsMu.Lock()
|
||||||
|
defer s.endpointsMu.Unlock()
|
||||||
|
|
||||||
|
delete(s.endpoints, fakeIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
||||||
|
func (s *RelayBindJS) GetICEMux() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
|
return nil, ErrUDPMUXNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RelayBindJS) ActivityRecorder() *ActivityRecorder {
|
||||||
|
return s.activityRecorder
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build linux || windows || freebsd
|
//go:build linux || windows || freebsd || js || wasip1
|
||||||
|
|
||||||
package configurer
|
package configurer
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !windows
|
//go:build !windows && !js
|
||||||
|
|
||||||
package configurer
|
package configurer
|
||||||
|
|
||||||
|
|||||||
23
client/iface/configurer/uapi_js.go
Normal file
23
client/iface/configurer/uapi_js.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package configurer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noopListener struct{}
|
||||||
|
|
||||||
|
func (n *noopListener) Accept() (net.Conn, error) {
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *noopListener) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *noopListener) Addr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openUAPI(deviceName string) (net.Listener, error) {
|
||||||
|
return &noopListener{}, nil
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
@@ -17,6 +19,12 @@ import (
|
|||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Bind interface {
|
||||||
|
conn.Bind
|
||||||
|
GetICEMux() (*udpmux.UniversalUDPMuxDefault, error)
|
||||||
|
ActivityRecorder() *bind.ActivityRecorder
|
||||||
|
}
|
||||||
|
|
||||||
type TunNetstackDevice struct {
|
type TunNetstackDevice struct {
|
||||||
name string
|
name string
|
||||||
address wgaddr.Address
|
address wgaddr.Address
|
||||||
@@ -24,7 +32,7 @@ type TunNetstackDevice struct {
|
|||||||
key string
|
key string
|
||||||
mtu int
|
mtu int
|
||||||
listenAddress string
|
listenAddress string
|
||||||
iceBind *bind.ICEBind
|
bind Bind
|
||||||
|
|
||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
@@ -35,7 +43,7 @@ type TunNetstackDevice struct {
|
|||||||
net *netstack.Net
|
net *netstack.Net
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetstackDevice(name string, address wgaddr.Address, wgPort int, key string, mtu int, iceBind *bind.ICEBind, listenAddress string) *TunNetstackDevice {
|
func NewNetstackDevice(name string, address wgaddr.Address, wgPort int, key string, mtu uint16, bind Bind, listenAddress string) *TunNetstackDevice {
|
||||||
return &TunNetstackDevice{
|
return &TunNetstackDevice{
|
||||||
name: name,
|
name: name,
|
||||||
address: address,
|
address: address,
|
||||||
@@ -43,7 +51,7 @@ func NewNetstackDevice(name string, address wgaddr.Address, wgPort int, key stri
|
|||||||
key: key,
|
key: key,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
listenAddress: listenAddress,
|
listenAddress: listenAddress,
|
||||||
iceBind: iceBind,
|
bind: bind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +76,11 @@ func (t *TunNetstackDevice) Create() (WGConfigurer, error) {
|
|||||||
|
|
||||||
t.device = device.NewDevice(
|
t.device = device.NewDevice(
|
||||||
t.filteredDevice,
|
t.filteredDevice,
|
||||||
t.iceBind,
|
t.bind,
|
||||||
device.NewLogger(wgLogLevel(), "[netbird] "),
|
device.NewLogger(wgLogLevel(), "[netbird] "),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
|
t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.bind.ActivityRecorder())
|
||||||
err = t.configurer.ConfigureInterface(t.key, t.port)
|
err = t.configurer.ConfigureInterface(t.key, t.port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tunIface.Close()
|
_ = tunIface.Close()
|
||||||
@@ -93,11 +101,15 @@ func (t *TunNetstackDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
udpMux, err := t.iceBind.GetICEMux()
|
udpMux, err := t.bind.GetICEMux()
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, bind.ErrUDPMUXNotSupported) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
t.udpMux = udpMux
|
|
||||||
|
if udpMux != nil {
|
||||||
|
t.udpMux = udpMux
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("netstack device is ready to use")
|
log.Debugf("netstack device is ready to use")
|
||||||
return udpMux, nil
|
return udpMux, nil
|
||||||
}
|
}
|
||||||
|
|||||||
27
client/iface/device/device_netstack_test.go
Normal file
27
client/iface/device/device_netstack_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewNetstackDevice(t *testing.T) {
|
||||||
|
privateKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
wgAddress, _ := wgaddr.ParseWGAddress("1.2.3.4/24")
|
||||||
|
|
||||||
|
relayBind := bind.NewRelayBindJS()
|
||||||
|
nsTun := NewNetstackDevice("wtx", wgAddress, 1234, privateKey.String(), 1500, relayBind, netstack.ListenAddr())
|
||||||
|
|
||||||
|
cfgr, err := nsTun.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create netstack device: %v", err)
|
||||||
|
}
|
||||||
|
if cfgr == nil {
|
||||||
|
t.Fatal("expected non-nil configurer")
|
||||||
|
}
|
||||||
|
}
|
||||||
6
client/iface/iface_destroy_js.go
Normal file
6
client/iface/iface_destroy_js.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
// Destroy is a no-op on WASM
|
||||||
|
func (w *WGIface) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package iface
|
|||||||
import (
|
import (
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
)
|
)
|
||||||
@@ -14,12 +15,21 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
|
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress, opts.MTU)
|
||||||
|
|
||||||
|
if netstack.IsEnabled() {
|
||||||
|
wgIFace := &WGIface{
|
||||||
|
userspaceBind: true,
|
||||||
|
tun: device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr()),
|
||||||
|
wgProxyFactory: wgproxy.NewUSPFactory(iceBind, opts.MTU),
|
||||||
|
}
|
||||||
|
return wgIFace, nil
|
||||||
|
}
|
||||||
|
|
||||||
wgIFace := &WGIface{
|
wgIFace := &WGIface{
|
||||||
userspaceBind: true,
|
userspaceBind: true,
|
||||||
tun: device.NewTunDevice(wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, opts.MobileArgs.TunAdapter, opts.DisableDNS),
|
tun: device.NewTunDevice(wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, opts.MobileArgs.TunAdapter, opts.DisableDNS),
|
||||||
wgProxyFactory: wgproxy.NewUSPFactory(iceBind),
|
wgProxyFactory: wgproxy.NewUSPFactory(iceBind, opts.MTU),
|
||||||
}
|
}
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
wgIFace := &WGIface{
|
wgIFace := &WGIface{
|
||||||
userspaceBind: true,
|
userspaceBind: true,
|
||||||
tun: tun,
|
tun: tun,
|
||||||
wgProxyFactory: wgproxy.NewUSPFactory(iceBind),
|
wgProxyFactory: wgproxy.NewUSPFactory(iceBind, opts.MTU),
|
||||||
}
|
}
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|||||||
41
client/iface/iface_new_freebsd.go
Normal file
41
client/iface/iface_new_freebsd.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
|
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
||||||
|
wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wgIFace := &WGIface{}
|
||||||
|
|
||||||
|
if netstack.IsEnabled() {
|
||||||
|
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress, opts.MTU)
|
||||||
|
wgIFace.tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr())
|
||||||
|
wgIFace.userspaceBind = true
|
||||||
|
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind, opts.MTU)
|
||||||
|
return wgIFace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.ModuleTunIsLoaded() {
|
||||||
|
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress, opts.MTU)
|
||||||
|
wgIFace.tun = device.NewUSPDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind)
|
||||||
|
wgIFace.userspaceBind = true
|
||||||
|
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind, opts.MTU)
|
||||||
|
return wgIFace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("couldn't check or load tun module")
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
wgIFace := &WGIface{
|
wgIFace := &WGIface{
|
||||||
tun: device.NewTunDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, iceBind, opts.MobileArgs.TunFd),
|
tun: device.NewTunDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, iceBind, opts.MobileArgs.TunFd),
|
||||||
userspaceBind: true,
|
userspaceBind: true,
|
||||||
wgProxyFactory: wgproxy.NewUSPFactory(iceBind),
|
wgProxyFactory: wgproxy.NewUSPFactory(iceBind, opts.MTU),
|
||||||
}
|
}
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|||||||
27
client/iface/iface_new_js.go
Normal file
27
client/iface/iface_new_js.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWGIFace creates a new WireGuard interface for WASM (always uses netstack mode)
|
||||||
|
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
||||||
|
wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
relayBind := bind.NewRelayBindJS()
|
||||||
|
|
||||||
|
wgIface := &WGIface{
|
||||||
|
tun: device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, relayBind, netstack.ListenAddr()),
|
||||||
|
userspaceBind: true,
|
||||||
|
wgProxyFactory: wgproxy.NewUSPFactory(relayBind, opts.MTU),
|
||||||
|
}
|
||||||
|
|
||||||
|
return wgIface, nil
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
|
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
|
||||||
wgIFace.tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr())
|
wgIFace.tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr())
|
||||||
wgIFace.userspaceBind = true
|
wgIFace.userspaceBind = true
|
||||||
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind)
|
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind, opts.MTU)
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
|
iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
|
||||||
wgIFace.tun = device.NewUSPDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind)
|
wgIFace.tun = device.NewUSPDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind)
|
||||||
wgIFace.userspaceBind = true
|
wgIFace.userspaceBind = true
|
||||||
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind)
|
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind, opts.MTU)
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
wgIFace := &WGIface{
|
wgIFace := &WGIface{
|
||||||
userspaceBind: true,
|
userspaceBind: true,
|
||||||
tun: tun,
|
tun: tun,
|
||||||
wgProxyFactory: wgproxy.NewUSPFactory(iceBind),
|
wgProxyFactory: wgproxy.NewUSPFactory(iceBind, opts.MTU),
|
||||||
}
|
}
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !js
|
||||||
|
|
||||||
package netstack
|
package netstack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
12
client/iface/netstack/env_js.go
Normal file
12
client/iface/netstack/env_js.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package netstack
|
||||||
|
|
||||||
|
const EnvUseNetstackMode = "NB_USE_NETSTACK_MODE"
|
||||||
|
|
||||||
|
// IsEnabled always returns true for js since it's the only mode available
|
||||||
|
func IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenAddr() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -15,28 +15,38 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface/wgproxy/listener"
|
"github.com/netbirdio/netbird/client/iface/wgproxy/listener"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyBind struct {
|
type Bind interface {
|
||||||
Bind *bind.ICEBind
|
SetEndpoint(addr netip.Addr, conn net.Conn)
|
||||||
|
RemoveEndpoint(addr netip.Addr)
|
||||||
|
ReceiveFromEndpoint(ctx context.Context, ep *bind.Endpoint, buf []byte)
|
||||||
|
}
|
||||||
|
|
||||||
fakeNetIP *netip.AddrPort
|
type ProxyBind struct {
|
||||||
wgBindEndpoint *bind.Endpoint
|
bind Bind
|
||||||
remoteConn net.Conn
|
|
||||||
ctx context.Context
|
// wgRelayedEndpoint is a fake address that generated by the Bind.SetEndpoint based on the remote NetBird peer address
|
||||||
cancel context.CancelFunc
|
wgRelayedEndpoint *bind.Endpoint
|
||||||
closeMu sync.Mutex
|
wgCurrentUsed *bind.Endpoint
|
||||||
closed bool
|
remoteConn net.Conn
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
closeMu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
|
||||||
pausedMu sync.Mutex
|
pausedMu sync.Mutex
|
||||||
paused bool
|
paused bool
|
||||||
isStarted bool
|
isStarted bool
|
||||||
|
|
||||||
closeListener *listener.CloseListener
|
closeListener *listener.CloseListener
|
||||||
|
mtu uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyBind(bind *bind.ICEBind) *ProxyBind {
|
func NewProxyBind(bind Bind, mtu uint16) *ProxyBind {
|
||||||
p := &ProxyBind{
|
p := &ProxyBind{
|
||||||
Bind: bind,
|
Bind: bind,
|
||||||
closeListener: listener.NewCloseListener(),
|
closeListener: listener.NewCloseListener(),
|
||||||
|
pausedCond: sync.NewCond(&sync.Mutex{}),
|
||||||
|
mtu: mtu + bufsize.WGBufferOverhead,
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
@@ -135,7 +145,7 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 1500)
|
buf := make([]byte, p.mtu)
|
||||||
n, err := p.remoteConn.Read(buf)
|
n, err := p.remoteConn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
@@ -152,12 +162,8 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := bind.RecvMessage{
|
p.bind.ReceiveFromEndpoint(ctx, p.wgCurrentUsed, buf[:n])
|
||||||
Endpoint: p.wgBindEndpoint,
|
p.pausedCond.L.Unlock()
|
||||||
Buffer: buf[:n],
|
|
||||||
}
|
|
||||||
p.Bind.RecvChan <- msg
|
|
||||||
p.pausedMu.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,24 +3,25 @@ package wgproxy
|
|||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
proxyBind "github.com/netbirdio/netbird/client/iface/wgproxy/bind"
|
proxyBind "github.com/netbirdio/netbird/client/iface/wgproxy/bind"
|
||||||
)
|
)
|
||||||
|
|
||||||
type USPFactory struct {
|
type USPFactory struct {
|
||||||
bind *bind.ICEBind
|
bind proxyBind.Bind
|
||||||
|
mtu uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUSPFactory(iceBind *bind.ICEBind) *USPFactory {
|
func NewUSPFactory(bind proxyBind.Bind, mtu uint16) *USPFactory {
|
||||||
log.Infof("WireGuard Proxy Factory will produce bind proxy")
|
log.Infof("WireGuard Proxy Factory will produce bind proxy")
|
||||||
f := &USPFactory{
|
f := &USPFactory{
|
||||||
bind: iceBind,
|
bind: bind,
|
||||||
|
mtu: mtu,
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *USPFactory) GetProxy() Proxy {
|
func (w *USPFactory) GetProxy() Proxy {
|
||||||
return proxyBind.NewProxyBind(w.bind)
|
return proxyBind.NewProxyBind(w.bind, w.mtu)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *USPFactory) Free() error {
|
func (w *USPFactory) Free() error {
|
||||||
|
|||||||
@@ -27,30 +27,30 @@ func TestProxyCloseByRemoteConnEBPF(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
tests := []struct {
|
pUDP := proxyInstance{
|
||||||
name string
|
name: "udp kernel proxy",
|
||||||
proxy Proxy
|
proxy: udp.NewWGUDPProxy(51832, 1280),
|
||||||
}{
|
wgPort: 51832,
|
||||||
{
|
closeFn: func() error { return nil },
|
||||||
name: "ebpf proxy",
|
}
|
||||||
proxy: &ebpf.ProxyWrapper{
|
pl = append(pl, pUDP)
|
||||||
WgeBPFProxy: ebpfProxy,
|
wgAddress, err := wgaddr.ParseWGAddress("10.0.0.1/32")
|
||||||
},
|
if err != nil {
|
||||||
},
|
return nil, err
|
||||||
|
}
|
||||||
|
iceBind := bind.NewICEBind(nil, nil, wgAddress, 1280)
|
||||||
|
endpointAddress := &net.UDPAddr{
|
||||||
|
IP: net.IPv4(10, 0, 0, 1),
|
||||||
|
Port: 1234,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
pBind := proxyInstance{
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
name: "bind proxy",
|
||||||
relayedConn := newMockConn()
|
proxy: bindproxy.NewProxyBind(iceBind, 0),
|
||||||
err := tt.proxy.AddTurnConn(ctx, nil, relayedConn)
|
endpointAddr: endpointAddress,
|
||||||
if err != nil {
|
closeFn: func() error { return nil },
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = relayedConn.Close()
|
|
||||||
if err := tt.proxy.CloseConn(); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
pl = append(pl, pBind)
|
||||||
|
|
||||||
|
return pl, nil
|
||||||
}
|
}
|
||||||
|
|||||||
39
client/iface/wgproxy/proxy_seed_test.go
Normal file
39
client/iface/wgproxy/proxy_seed_test.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
|
bindproxy "github.com/netbirdio/netbird/client/iface/wgproxy/bind"
|
||||||
|
)
|
||||||
|
|
||||||
|
func seedProxies() ([]proxyInstance, error) {
|
||||||
|
// todo extend with Bind proxy
|
||||||
|
pl := make([]proxyInstance, 0)
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedProxyForProxyCloseByRemoteConn() ([]proxyInstance, error) {
|
||||||
|
pl := make([]proxyInstance, 0)
|
||||||
|
wgAddress, err := wgaddr.ParseWGAddress("10.0.0.1/32")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iceBind := bind.NewICEBind(nil, nil, wgAddress, 1280)
|
||||||
|
endpointAddress := &net.UDPAddr{
|
||||||
|
IP: net.IPv4(10, 0, 0, 1),
|
||||||
|
Port: 1234,
|
||||||
|
}
|
||||||
|
|
||||||
|
pBind := proxyInstance{
|
||||||
|
name: "bind proxy",
|
||||||
|
proxy: bindproxy.NewProxyBind(iceBind, 0),
|
||||||
|
endpointAddr: endpointAddress,
|
||||||
|
closeFn: func() error { return nil },
|
||||||
|
}
|
||||||
|
pl = append(pl, pBind)
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
!define WEB_SITE "Netbird.io"
|
!define WEB_SITE "Netbird.io"
|
||||||
!define VERSION $%APPVER%
|
!define VERSION $%APPVER%
|
||||||
!define COPYRIGHT "Netbird Authors, 2022"
|
!define COPYRIGHT "Netbird Authors, 2022"
|
||||||
!define DESCRIPTION "A WireGuard®-based mesh network that connects your devices into a single private network"
|
!define DESCRIPTION "Connect your devices into a secure WireGuard-based overlay network with SSO, MFA, and granular access controls."
|
||||||
!define INSTALLER_NAME "netbird-installer.exe"
|
!define INSTALLER_NAME "netbird-installer.exe"
|
||||||
!define MAIN_APP_EXE "Netbird"
|
!define MAIN_APP_EXE "Netbird"
|
||||||
!define ICON "ui\\assets\\netbird.ico"
|
!define ICON "ui\\assets\\netbird.ico"
|
||||||
@@ -59,9 +59,15 @@ ShowInstDetails Show
|
|||||||
!define MUI_UNICON "${ICON}"
|
!define MUI_UNICON "${ICON}"
|
||||||
!define MUI_WELCOMEFINISHPAGE_BITMAP "${BANNER}"
|
!define MUI_WELCOMEFINISHPAGE_BITMAP "${BANNER}"
|
||||||
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${BANNER}"
|
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${BANNER}"
|
||||||
!define MUI_FINISHPAGE_RUN
|
!ifndef ARCH
|
||||||
!define MUI_FINISHPAGE_RUN_TEXT "Start ${UI_APP_NAME}"
|
!define ARCH "amd64"
|
||||||
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
|
!endif
|
||||||
|
|
||||||
|
!if ${ARCH} == "amd64"
|
||||||
|
!define MUI_FINISHPAGE_RUN
|
||||||
|
!define MUI_FINISHPAGE_RUN_TEXT "Start ${UI_APP_NAME}"
|
||||||
|
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
|
||||||
|
!endif
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
!define MUI_ABORTWARNING
|
!define MUI_ABORTWARNING
|
||||||
@@ -213,7 +219,15 @@ Section -MainProgram
|
|||||||
${INSTALL_TYPE}
|
${INSTALL_TYPE}
|
||||||
# SetOverwrite ifnewer
|
# SetOverwrite ifnewer
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
File /r "..\\dist\\netbird_windows_amd64\\"
|
!ifndef ARCH
|
||||||
|
!define ARCH "amd64"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if ${ARCH} == "arm64"
|
||||||
|
File /r "..\\dist\\netbird_windows_arm64\\"
|
||||||
|
!else
|
||||||
|
File /r "..\\dist\\netbird_windows_amd64\\"
|
||||||
|
!endif
|
||||||
SectionEnd
|
SectionEnd
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
@@ -292,7 +306,9 @@ DetailPrint "Deleting application files..."
|
|||||||
Delete "$INSTDIR\${UI_APP_EXE}"
|
Delete "$INSTDIR\${UI_APP_EXE}"
|
||||||
Delete "$INSTDIR\${MAIN_APP_EXE}"
|
Delete "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
Delete "$INSTDIR\wintun.dll"
|
Delete "$INSTDIR\wintun.dll"
|
||||||
|
!if ${ARCH} == "amd64"
|
||||||
Delete "$INSTDIR\opengl32.dll"
|
Delete "$INSTDIR\opengl32.dll"
|
||||||
|
!endif
|
||||||
DetailPrint "Removing application directory..."
|
DetailPrint "Removing application directory..."
|
||||||
RmDir /r "$INSTDIR"
|
RmDir /r "$INSTDIR"
|
||||||
|
|
||||||
@@ -314,8 +330,10 @@ DetailPrint "Uninstallation finished."
|
|||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
|
|
||||||
|
!if ${ARCH} == "amd64"
|
||||||
Function LaunchLink
|
Function LaunchLink
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
|
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
!endif
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -44,17 +45,19 @@ type ConnectClient struct {
|
|||||||
engineMutex sync.Mutex
|
engineMutex sync.Mutex
|
||||||
|
|
||||||
persistSyncResponse bool
|
persistSyncResponse bool
|
||||||
|
LogFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnectClient(
|
func NewConnectClient(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *profilemanager.Config,
|
config *profilemanager.Config,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
|
logFile string,
|
||||||
) *ConnectClient {
|
) *ConnectClient {
|
||||||
return &ConnectClient{
|
return &ConnectClient{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
config: config,
|
config: config,
|
||||||
|
LogFile: logFile,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
engineMutex: sync.Mutex{},
|
engineMutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
@@ -70,7 +73,7 @@ func (c *ConnectClient) RunOnAndroid(
|
|||||||
tunAdapter device.TunAdapter,
|
tunAdapter device.TunAdapter,
|
||||||
iFaceDiscover stdnet.ExternalIFaceDiscover,
|
iFaceDiscover stdnet.ExternalIFaceDiscover,
|
||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
dnsAddresses []string,
|
dnsAddresses []netip.AddrPort,
|
||||||
dnsReadyListener dns.ReadyListener,
|
dnsReadyListener dns.ReadyListener,
|
||||||
) error {
|
) error {
|
||||||
// in case of non Android os these variables will be nil
|
// in case of non Android os these variables will be nil
|
||||||
@@ -260,7 +263,7 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
|||||||
|
|
||||||
peerConfig := loginResp.GetPeerConfig()
|
peerConfig := loginResp.GetPeerConfig()
|
||||||
|
|
||||||
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig)
|
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig, c.LogFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
@@ -269,7 +272,7 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
|||||||
checks := loginResp.GetChecks()
|
checks := loginResp.GetChecks()
|
||||||
|
|
||||||
c.engineMutex.Lock()
|
c.engineMutex.Lock()
|
||||||
c.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)
|
||||||
c.engine.SetSyncResponsePersistence(c.persistSyncResponse)
|
c.engine.SetSyncResponsePersistence(c.persistSyncResponse)
|
||||||
c.engineMutex.Unlock()
|
c.engineMutex.Unlock()
|
||||||
|
|
||||||
@@ -414,7 +417,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, logFile string) (*EngineConfig, error) {
|
||||||
nm := false
|
nm := false
|
||||||
if config.NetworkMonitor != nil {
|
if config.NetworkMonitor != nil {
|
||||||
nm = *config.NetworkMonitor
|
nm = *config.NetworkMonitor
|
||||||
@@ -443,6 +446,9 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf
|
|||||||
BlockInbound: config.BlockInbound,
|
BlockInbound: config.BlockInbound,
|
||||||
|
|
||||||
LazyConnectionEnabled: config.LazyConnectionEnabled,
|
LazyConnectionEnabled: config.LazyConnectionEnabled,
|
||||||
|
LogFile: logFile,
|
||||||
|
|
||||||
|
ProfileConfig: config,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.PreSharedKey != "" {
|
if config.PreSharedKey != "" {
|
||||||
|
|||||||
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+"/")
|
||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type resolvConf struct {
|
type resolvConf struct {
|
||||||
nameServers []string
|
nameServers []netip.Addr
|
||||||
searchDomains []string
|
searchDomains []string
|
||||||
others []string
|
others []string
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ func parseBackupResolvConf() (*resolvConf, error) {
|
|||||||
func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
||||||
rconf := &resolvConf{
|
rconf := &resolvConf{
|
||||||
searchDomains: make([]string, 0),
|
searchDomains: make([]string, 0),
|
||||||
nameServers: make([]string, 0),
|
nameServers: make([]netip.Addr, 0),
|
||||||
others: make([]string, 0),
|
others: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,11 @@ func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
|||||||
if len(splitLines) != 2 {
|
if len(splitLines) != 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rconf.nameServers = append(rconf.nameServers, splitLines[1])
|
if addr, err := netip.ParseAddr(splitLines[1]); err == nil {
|
||||||
|
rconf.nameServers = append(rconf.nameServers, addr.Unmap())
|
||||||
|
} else {
|
||||||
|
log.Warnf("invalid nameserver address in resolv.conf: %s, skipping", splitLines[1])
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,31 +108,3 @@ func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
|||||||
}
|
}
|
||||||
return rconf, nil
|
return rconf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeFirstNbNameserver removes the given nameserver from the given file if it is in the first position
|
|
||||||
// and writes the file back to the original location
|
|
||||||
func removeFirstNbNameserver(filename string, nameserverIP netip.Addr) error {
|
|
||||||
resolvConf, err := parseResolvConfFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse backup resolv.conf: %w", err)
|
|
||||||
}
|
|
||||||
content, err := os.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("read %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resolvConf.nameServers) > 1 && resolvConf.nameServers[0] == nameserverIP.String() {
|
|
||||||
newContent := strings.Replace(string(content), fmt.Sprintf("nameserver %s\n", nameserverIP), "", 1)
|
|
||||||
|
|
||||||
stat, err := os.Stat(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("stat %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(filename, []byte(newContent), stat.Mode()); err != nil {
|
|
||||||
return fmt.Errorf("write %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,13 +3,9 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_parseResolvConf(t *testing.T) {
|
func Test_parseResolvConf(t *testing.T) {
|
||||||
@@ -99,9 +95,13 @@ options debug
|
|||||||
t.Errorf("invalid parse result for search domains, expected: %v, got: %v", testCase.expectedSearch, cfg.searchDomains)
|
t.Errorf("invalid parse result for search domains, expected: %v, got: %v", testCase.expectedSearch, cfg.searchDomains)
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = compareLists(cfg.nameServers, testCase.expectedNS)
|
nsStrings := make([]string, len(cfg.nameServers))
|
||||||
|
for i, ns := range cfg.nameServers {
|
||||||
|
nsStrings[i] = ns.String()
|
||||||
|
}
|
||||||
|
ok = compareLists(nsStrings, testCase.expectedNS)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("invalid parse result for ns domains, expected: %v, got: %v", testCase.expectedNS, cfg.nameServers)
|
t.Errorf("invalid parse result for ns domains, expected: %v, got: %v", testCase.expectedNS, nsStrings)
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = compareLists(cfg.others, testCase.expectedOther)
|
ok = compareLists(cfg.others, testCase.expectedOther)
|
||||||
@@ -176,87 +176,3 @@ nameserver 192.168.0.1
|
|||||||
t.Errorf("unexpected resolv.conf content: %v", cfg)
|
t.Errorf("unexpected resolv.conf content: %v", cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveFirstNbNameserver(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
content string
|
|
||||||
ipToRemove string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Unrelated nameservers with comments and options",
|
|
||||||
content: `# This is a comment
|
|
||||||
options rotate
|
|
||||||
nameserver 1.1.1.1
|
|
||||||
# Another comment
|
|
||||||
nameserver 8.8.4.4
|
|
||||||
search example.com`,
|
|
||||||
ipToRemove: "9.9.9.9",
|
|
||||||
expected: `# This is a comment
|
|
||||||
options rotate
|
|
||||||
nameserver 1.1.1.1
|
|
||||||
# Another comment
|
|
||||||
nameserver 8.8.4.4
|
|
||||||
search example.com`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "First nameserver matches",
|
|
||||||
content: `search example.com
|
|
||||||
nameserver 9.9.9.9
|
|
||||||
# oof, a comment
|
|
||||||
nameserver 8.8.4.4
|
|
||||||
options attempts:5`,
|
|
||||||
ipToRemove: "9.9.9.9",
|
|
||||||
expected: `search example.com
|
|
||||||
# oof, a comment
|
|
||||||
nameserver 8.8.4.4
|
|
||||||
options attempts:5`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Target IP not the first nameserver",
|
|
||||||
// nolint:dupword
|
|
||||||
content: `# Comment about the first nameserver
|
|
||||||
nameserver 8.8.4.4
|
|
||||||
# Comment before our target
|
|
||||||
nameserver 9.9.9.9
|
|
||||||
options timeout:2`,
|
|
||||||
ipToRemove: "9.9.9.9",
|
|
||||||
// nolint:dupword
|
|
||||||
expected: `# Comment about the first nameserver
|
|
||||||
nameserver 8.8.4.4
|
|
||||||
# Comment before our target
|
|
||||||
nameserver 9.9.9.9
|
|
||||||
options timeout:2`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Only nameserver matches",
|
|
||||||
content: `options debug
|
|
||||||
nameserver 9.9.9.9
|
|
||||||
search localdomain`,
|
|
||||||
ipToRemove: "9.9.9.9",
|
|
||||||
expected: `options debug
|
|
||||||
nameserver 9.9.9.9
|
|
||||||
search localdomain`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
tempFile := filepath.Join(tempDir, "resolv.conf")
|
|
||||||
err := os.WriteFile(tempFile, []byte(tc.content), 0644)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
ip, err := netip.ParseAddr(tc.ipToRemove)
|
|
||||||
require.NoError(t, err, "Failed to parse IP address")
|
|
||||||
err = removeFirstNbNameserver(tempFile, ip)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
content, err := os.ReadFile(tempFile)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, tc.expected, string(content), "The resulting content should match the expected output.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func isNbParamsMissing(nbSearchDomains []string, nbNameserverIP netip.Addr, rCon
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if rConf.nameServers[0] != nbNameserverIP.String() {
|
if rConf.nameServers[0] != nbNameserverIP {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type fileConfigurator struct {
|
|||||||
repair *repair
|
repair *repair
|
||||||
originalPerms os.FileMode
|
originalPerms os.FileMode
|
||||||
nbNameserverIP netip.Addr
|
nbNameserverIP netip.Addr
|
||||||
originalNameservers []string
|
originalNameservers []netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileConfigurator() (*fileConfigurator, error) {
|
func newFileConfigurator() (*fileConfigurator, error) {
|
||||||
@@ -70,7 +70,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getOriginalNameservers returns the nameservers that were found in the original resolv.conf
|
// getOriginalNameservers returns the nameservers that were found in the original resolv.conf
|
||||||
func (f *fileConfigurator) getOriginalNameservers() []string {
|
func (f *fileConfigurator) getOriginalNameservers() []netip.Addr {
|
||||||
return f.originalNameservers
|
return f.originalNameservers
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,20 +128,14 @@ func (f *fileConfigurator) backup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileConfigurator) restore() error {
|
func (f *fileConfigurator) restore() error {
|
||||||
err := removeFirstNbNameserver(fileDefaultResolvConfBackupLocation, f.nbNameserverIP)
|
if err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to remove netbird nameserver from %s on backup restore: %s", fileDefaultResolvConfBackupLocation, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.RemoveAll(fileDefaultResolvConfBackupLocation)
|
return os.RemoveAll(fileDefaultResolvConfBackupLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Addr) error {
|
func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress netip.Addr) error {
|
||||||
resolvConf, err := parseDefaultResolvConf()
|
resolvConf, err := parseDefaultResolvConf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse current resolv.conf: %w", err)
|
return fmt.Errorf("parse current resolv.conf: %w", err)
|
||||||
@@ -152,16 +146,9 @@ func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Add
|
|||||||
return restoreResolvConfFile()
|
return restoreResolvConfFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0])
|
|
||||||
// not a valid first nameserver -> restore
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[0], err)
|
|
||||||
return restoreResolvConfFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// current address is still netbird's non-available dns address -> restore
|
// current address is still netbird's non-available dns address -> restore
|
||||||
// comparing parsed addresses only, to remove ambiguity
|
currentDNSAddress := resolvConf.nameServers[0]
|
||||||
if currentDNSAddress.String() == storedDNSAddress.String() {
|
if currentDNSAddress == storedDNSAddress {
|
||||||
return restoreResolvConfFile()
|
return restoreResolvConfFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ func (s *systemConfigurator) getSystemDNSSettings() (SystemDNSSettings, error) {
|
|||||||
} else if inServerAddressesArray {
|
} else if inServerAddressesArray {
|
||||||
address := strings.Split(line, " : ")[1]
|
address := strings.Split(line, " : ")[1]
|
||||||
if ip, err := netip.ParseAddr(address); err == nil && ip.Is4() {
|
if ip, err := netip.ParseAddr(address); err == nil && ip.Is4() {
|
||||||
dnsSettings.ServerIP = ip
|
dnsSettings.ServerIP = ip.Unmap()
|
||||||
inServerAddressesArray = false // Stop reading after finding the first IPv4 address
|
inServerAddressesArray = false // Stop reading after finding the first IPv4 address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ func (s *systemConfigurator) getSystemDNSSettings() (SystemDNSSettings, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default to 53 port
|
// default to 53 port
|
||||||
dnsSettings.ServerPort = defaultPort
|
dnsSettings.ServerPort = DefaultPort
|
||||||
|
|
||||||
return dnsSettings, nil
|
return dnsSettings, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (t osManagerType) String() string {
|
|||||||
|
|
||||||
type restoreHostManager interface {
|
type restoreHostManager interface {
|
||||||
hostManager
|
hostManager
|
||||||
restoreUncleanShutdownDNS(*netip.Addr) error
|
restoreUncleanShutdownDNS(netip.Addr) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostManager(wgInterface string) (hostManager, error) {
|
func newHostManager(wgInterface string) (hostManager, error) {
|
||||||
@@ -130,8 +130,9 @@ func checkStub() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
systemdResolvedAddr := netip.AddrFrom4([4]byte{127, 0, 0, 53}) // 127.0.0.53
|
||||||
for _, ns := range rConf.nameServers {
|
for _, ns := range rConf.nameServers {
|
||||||
if ns == "127.0.0.53" {
|
if ns == systemdResolvedAddr {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,9 +64,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type registryConfigurator struct {
|
type registryConfigurator struct {
|
||||||
guid string
|
guid string
|
||||||
routingAll bool
|
routingAll bool
|
||||||
gpo bool
|
gpo bool
|
||||||
|
nrptEntryCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
|
func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
|
||||||
@@ -177,7 +178,11 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
|
|||||||
log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP)
|
log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stateManager.UpdateState(&ShutdownState{Guid: r.guid, GPO: r.gpo}); err != nil {
|
if err := stateManager.UpdateState(&ShutdownState{
|
||||||
|
Guid: r.guid,
|
||||||
|
GPO: r.gpo,
|
||||||
|
NRPTEntryCount: r.nrptEntryCount,
|
||||||
|
}); err != nil {
|
||||||
log.Errorf("failed to update shutdown state: %s", err)
|
log.Errorf("failed to update shutdown state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,13 +198,24 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(matchDomains) != 0 {
|
if len(matchDomains) != 0 {
|
||||||
if err := r.addDNSMatchPolicy(matchDomains, config.ServerIP); err != nil {
|
count, err := r.addDNSMatchPolicy(matchDomains, config.ServerIP)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("add dns match policy: %w", err)
|
return fmt.Errorf("add dns match policy: %w", err)
|
||||||
}
|
}
|
||||||
|
r.nrptEntryCount = count
|
||||||
} else {
|
} else {
|
||||||
if err := r.removeDNSMatchPolicies(); err != nil {
|
if err := r.removeDNSMatchPolicies(); err != nil {
|
||||||
return fmt.Errorf("remove dns match policies: %w", err)
|
return fmt.Errorf("remove dns match policies: %w", err)
|
||||||
}
|
}
|
||||||
|
r.nrptEntryCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stateManager.UpdateState(&ShutdownState{
|
||||||
|
Guid: r.guid,
|
||||||
|
GPO: r.gpo,
|
||||||
|
NRPTEntryCount: r.nrptEntryCount,
|
||||||
|
}); err != nil {
|
||||||
|
log.Errorf("failed to update shutdown state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.updateSearchDomains(searchDomains); err != nil {
|
if err := r.updateSearchDomains(searchDomains); err != nil {
|
||||||
@@ -216,32 +232,42 @@ func (r *registryConfigurator) addDNSSetupForAll(ip netip.Addr) error {
|
|||||||
return fmt.Errorf("adding dns setup for all failed: %w", err)
|
return fmt.Errorf("adding dns setup for all failed: %w", err)
|
||||||
}
|
}
|
||||||
r.routingAll = true
|
r.routingAll = true
|
||||||
log.Infof("configured %s:53 as main DNS forwarder for this peer", ip)
|
log.Infof("configured %s:%d as main DNS forwarder for this peer", ip, DefaultPort)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip netip.Addr) error {
|
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip netip.Addr) (int, error) {
|
||||||
// if the gpo key is present, we need to put our DNS settings there, otherwise our config might be ignored
|
// if the gpo key is present, we need to put our DNS settings there, otherwise our config might be ignored
|
||||||
// see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/8cc31cb9-20cb-4140-9e85-3e08703b4745
|
// see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/8cc31cb9-20cb-4140-9e85-3e08703b4745
|
||||||
if r.gpo {
|
for i, domain := range domains {
|
||||||
if err := r.configureDNSPolicy(gpoDnsPolicyConfigMatchPath, domains, ip); err != nil {
|
localPath := fmt.Sprintf("%s-%d", dnsPolicyConfigMatchPath, i)
|
||||||
return fmt.Errorf("configure GPO DNS policy: %w", err)
|
gpoPath := fmt.Sprintf("%s-%d", gpoDnsPolicyConfigMatchPath, i)
|
||||||
|
|
||||||
|
singleDomain := []string{domain}
|
||||||
|
|
||||||
|
if err := r.configureDNSPolicy(localPath, singleDomain, ip); err != nil {
|
||||||
|
return i, fmt.Errorf("configure DNS Local policy for domain %s: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.gpo {
|
||||||
|
if err := r.configureDNSPolicy(gpoPath, singleDomain, ip); err != nil {
|
||||||
|
return i, fmt.Errorf("configure gpo DNS policy: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("added NRPT entry for domain: %s", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.gpo {
|
||||||
if err := refreshGroupPolicy(); err != nil {
|
if err := refreshGroupPolicy(); err != nil {
|
||||||
log.Warnf("failed to refresh group policy: %v", err)
|
log.Warnf("failed to refresh group policy: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if err := r.configureDNSPolicy(dnsPolicyConfigMatchPath, domains, ip); err != nil {
|
|
||||||
return fmt.Errorf("configure local DNS policy: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("added %d match domains. Domain list: %s", len(domains), domains)
|
log.Infof("added %d separate NRPT entries. Domain list: %s", len(domains), domains)
|
||||||
return nil
|
return len(domains), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// configureDNSPolicy handles the actual configuration of a DNS policy at the specified path
|
|
||||||
func (r *registryConfigurator) configureDNSPolicy(policyPath string, domains []string, ip netip.Addr) error {
|
func (r *registryConfigurator) configureDNSPolicy(policyPath string, domains []string, ip netip.Addr) error {
|
||||||
if err := removeRegistryKeyFromDNSPolicyConfig(policyPath); err != nil {
|
if err := removeRegistryKeyFromDNSPolicyConfig(policyPath); err != nil {
|
||||||
return fmt.Errorf("remove existing dns policy: %w", err)
|
return fmt.Errorf("remove existing dns policy: %w", err)
|
||||||
@@ -374,12 +400,25 @@ func (r *registryConfigurator) restoreHostDNS() error {
|
|||||||
|
|
||||||
func (r *registryConfigurator) removeDNSMatchPolicies() error {
|
func (r *registryConfigurator) removeDNSMatchPolicies() error {
|
||||||
var merr *multierror.Error
|
var merr *multierror.Error
|
||||||
|
|
||||||
|
// Try to remove the base entries (for backward compatibility)
|
||||||
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
|
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("remove local registry key: %w", err))
|
merr = multierror.Append(merr, fmt.Errorf("remove local base entry: %w", err))
|
||||||
|
}
|
||||||
|
if err := removeRegistryKeyFromDNSPolicyConfig(gpoDnsPolicyConfigMatchPath); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("remove GPO base entry: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := removeRegistryKeyFromDNSPolicyConfig(gpoDnsPolicyConfigMatchPath); err != nil {
|
for i := 0; i < r.nrptEntryCount; i++ {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("remove GPO registry key: %w", err))
|
localPath := fmt.Sprintf("%s-%d", dnsPolicyConfigMatchPath, i)
|
||||||
|
gpoPath := fmt.Sprintf("%s-%d", gpoDnsPolicyConfigMatchPath, i)
|
||||||
|
|
||||||
|
if err := removeRegistryKeyFromDNSPolicyConfig(localPath); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("remove local entry %d: %w", i, err))
|
||||||
|
}
|
||||||
|
if err := removeRegistryKeyFromDNSPolicyConfig(gpoPath); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("remove GPO entry %d: %w", i, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := refreshGroupPolicy(); err != nil {
|
if err := refreshGroupPolicy(); err != nil {
|
||||||
|
|||||||
@@ -1,38 +1,31 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type hostsDNSHolder struct {
|
type hostsDNSHolder struct {
|
||||||
unprotectedDNSList map[string]struct{}
|
unprotectedDNSList map[netip.AddrPort]struct{}
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostsDNSHolder() *hostsDNSHolder {
|
func newHostsDNSHolder() *hostsDNSHolder {
|
||||||
return &hostsDNSHolder{
|
return &hostsDNSHolder{
|
||||||
unprotectedDNSList: make(map[string]struct{}),
|
unprotectedDNSList: make(map[netip.AddrPort]struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hostsDNSHolder) set(list []string) {
|
func (h *hostsDNSHolder) set(list []netip.AddrPort) {
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
h.unprotectedDNSList = make(map[string]struct{})
|
h.unprotectedDNSList = make(map[netip.AddrPort]struct{})
|
||||||
for _, dns := range list {
|
for _, addrPort := range list {
|
||||||
dnsAddr, err := h.normalizeAddress(dns)
|
h.unprotectedDNSList[addrPort] = struct{}{}
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
h.unprotectedDNSList[dnsAddr] = struct{}{}
|
|
||||||
}
|
}
|
||||||
h.mutex.Unlock()
|
h.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hostsDNSHolder) get() map[string]struct{} {
|
func (h *hostsDNSHolder) get() map[netip.AddrPort]struct{} {
|
||||||
h.mutex.RLock()
|
h.mutex.RLock()
|
||||||
l := h.unprotectedDNSList
|
l := h.unprotectedDNSList
|
||||||
h.mutex.RUnlock()
|
h.mutex.RUnlock()
|
||||||
@@ -40,24 +33,10 @@ func (h *hostsDNSHolder) get() map[string]struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:unused
|
//nolint:unused
|
||||||
func (h *hostsDNSHolder) isContain(upstream string) bool {
|
func (h *hostsDNSHolder) contains(upstream netip.AddrPort) bool {
|
||||||
h.mutex.RLock()
|
h.mutex.RLock()
|
||||||
defer h.mutex.RUnlock()
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
_, ok := h.unprotectedDNSList[upstream]
|
_, ok := h.unprotectedDNSList[upstream]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hostsDNSHolder) normalizeAddress(addr string) (string, error) {
|
|
||||||
a, err := netip.ParseAddr(addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("invalid upstream IP address: %s, error: %s", addr, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Is4() {
|
|
||||||
return fmt.Sprintf("%s:53", addr), nil
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("[%s]:53", addr), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (m *MockServer) DnsIP() netip.Addr {
|
|||||||
return netip.MustParseAddr("100.10.254.255")
|
return netip.MustParseAddr("100.10.254.255")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockServer) OnUpdatedHostDNSServer(strings []string) {
|
func (m *MockServer) OnUpdatedHostDNSServer(addrs []netip.AddrPort) {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ func (n *networkManagerDbusConfigurator) deleteConnectionSettings() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *networkManagerDbusConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
|
func (n *networkManagerDbusConfigurator) restoreUncleanShutdownDNS(netip.Addr) error {
|
||||||
if err := n.restoreHostDNS(); err != nil {
|
if err := n.restoreHostDNS(); err != nil {
|
||||||
return fmt.Errorf("restoring dns via network-manager: %w", err)
|
return fmt.Errorf("restoring dns via network-manager: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type resolvconf struct {
|
|||||||
implType resolvconfType
|
implType resolvconfType
|
||||||
|
|
||||||
originalSearchDomains []string
|
originalSearchDomains []string
|
||||||
originalNameServers []string
|
originalNameServers []netip.Addr
|
||||||
othersConfigs []string
|
othersConfigs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig, stateManager *stateman
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolvconf) getOriginalNameservers() []string {
|
func (r *resolvconf) getOriginalNameservers() []netip.Addr {
|
||||||
return r.originalNameServers
|
return r.originalNameServers
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ func (r *resolvconf) applyConfig(content bytes.Buffer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolvconf) restoreUncleanShutdownDNS(*netip.Addr) error {
|
func (r *resolvconf) restoreUncleanShutdownDNS(netip.Addr) error {
|
||||||
if err := r.restoreHostDNS(); err != nil {
|
if err := r.restoreHostDNS(); err != nil {
|
||||||
return fmt.Errorf("restoring dns for interface %s: %w", r.ifaceName, err)
|
return fmt.Errorf("restoring dns for interface %s: %w", r.ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ type Server interface {
|
|||||||
Stop()
|
Stop()
|
||||||
DnsIP() netip.Addr
|
DnsIP() netip.Addr
|
||||||
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
||||||
OnUpdatedHostDNSServer(strings []string)
|
OnUpdatedHostDNSServer(addrs []netip.AddrPort)
|
||||||
SearchDomains() []string
|
SearchDomains() []string
|
||||||
ProbeAvailability()
|
ProbeAvailability()
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ type nsGroupsByDomain struct {
|
|||||||
// hostManagerWithOriginalNS extends the basic hostManager interface
|
// hostManagerWithOriginalNS extends the basic hostManager interface
|
||||||
type hostManagerWithOriginalNS interface {
|
type hostManagerWithOriginalNS interface {
|
||||||
hostManager
|
hostManager
|
||||||
getOriginalNameservers() []string
|
getOriginalNameservers() []netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultServer dns server object
|
// DefaultServer dns server object
|
||||||
@@ -136,7 +136,7 @@ func NewDefaultServer(
|
|||||||
func NewDefaultServerPermanentUpstream(
|
func NewDefaultServerPermanentUpstream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
wgInterface WGIface,
|
wgInterface WGIface,
|
||||||
hostsDnsList []string,
|
hostsDnsList []netip.AddrPort,
|
||||||
config nbdns.Config,
|
config nbdns.Config,
|
||||||
listener listener.NetworkChangeListener,
|
listener listener.NetworkChangeListener,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
@@ -144,6 +144,7 @@ func NewDefaultServerPermanentUpstream(
|
|||||||
) *DefaultServer {
|
) *DefaultServer {
|
||||||
log.Debugf("host dns address list is: %v", hostsDnsList)
|
log.Debugf("host dns address list is: %v", hostsDnsList)
|
||||||
ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder, nil, disableSys)
|
ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder, nil, disableSys)
|
||||||
|
|
||||||
ds.hostsDNSHolder.set(hostsDnsList)
|
ds.hostsDNSHolder.set(hostsDnsList)
|
||||||
ds.permanent = true
|
ds.permanent = true
|
||||||
ds.addHostRootZone()
|
ds.addHostRootZone()
|
||||||
@@ -340,7 +341,7 @@ func (s *DefaultServer) disableDNS() error {
|
|||||||
|
|
||||||
// OnUpdatedHostDNSServer update the DNS servers addresses for root zones
|
// OnUpdatedHostDNSServer update the DNS servers addresses for root zones
|
||||||
// It will be applied if the mgm server do not enforce DNS settings for root zone
|
// It will be applied if the mgm server do not enforce DNS settings for root zone
|
||||||
func (s *DefaultServer) OnUpdatedHostDNSServer(hostsDnsList []string) {
|
func (s *DefaultServer) OnUpdatedHostDNSServer(hostsDnsList []netip.AddrPort) {
|
||||||
s.hostsDNSHolder.set(hostsDnsList)
|
s.hostsDNSHolder.set(hostsDnsList)
|
||||||
|
|
||||||
// Check if there's any root handler
|
// Check if there's any root handler
|
||||||
@@ -461,7 +462,7 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
|
|||||||
|
|
||||||
s.currentConfig = dnsConfigToHostDNSConfig(update, s.service.RuntimeIP(), s.service.RuntimePort())
|
s.currentConfig = dnsConfigToHostDNSConfig(update, s.service.RuntimeIP(), s.service.RuntimePort())
|
||||||
|
|
||||||
if s.service.RuntimePort() != defaultPort && !s.hostManager.supportCustomPort() {
|
if s.service.RuntimePort() != DefaultPort && !s.hostManager.supportCustomPort() {
|
||||||
log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " +
|
log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " +
|
||||||
"Learn more at: https://docs.netbird.io/how-to/manage-dns-in-your-network#local-resolver")
|
"Learn more at: https://docs.netbird.io/how-to/manage-dns-in-your-network#local-resolver")
|
||||||
s.currentConfig.RouteAll = false
|
s.currentConfig.RouteAll = false
|
||||||
@@ -581,14 +582,13 @@ func (s *DefaultServer) registerFallback(config HostDNSConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ns := range originalNameservers {
|
for _, ns := range originalNameservers {
|
||||||
if ns == config.ServerIP.String() {
|
if ns == config.ServerIP {
|
||||||
log.Debugf("skipping original nameserver %s as it is the same as the server IP %s", ns, config.ServerIP)
|
log.Debugf("skipping original nameserver %s as it is the same as the server IP %s", ns, config.ServerIP)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ns = formatAddr(ns, defaultPort)
|
addrPort := netip.AddrPortFrom(ns, DefaultPort)
|
||||||
|
handler.upstreamServers = append(handler.upstreamServers, addrPort)
|
||||||
handler.upstreamServers = append(handler.upstreamServers, ns)
|
|
||||||
}
|
}
|
||||||
handler.deactivate = func(error) { /* always active */ }
|
handler.deactivate = func(error) { /* always active */ }
|
||||||
handler.reactivate = func() { /* always active */ }
|
handler.reactivate = func() { /* always active */ }
|
||||||
@@ -695,7 +695,13 @@ func (s *DefaultServer) createHandlersForDomainGroup(domainGroup nsGroupsByDomai
|
|||||||
ns.IP.String(), ns.NSType.String(), nbdns.UDPNameServerType.String())
|
ns.IP.String(), ns.NSType.String(), nbdns.UDPNameServerType.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handler.upstreamServers = append(handler.upstreamServers, getNSHostPort(ns))
|
|
||||||
|
if ns.IP == s.service.RuntimeIP() {
|
||||||
|
log.Warnf("skipping nameserver %s as it matches our DNS server IP, preventing potential loop", ns.IP)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.upstreamServers = append(handler.upstreamServers, ns.AddrPort())
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(handler.upstreamServers) == 0 {
|
if len(handler.upstreamServers) == 0 {
|
||||||
@@ -770,18 +776,6 @@ func (s *DefaultServer) updateMux(muxUpdates []handlerWrapper) {
|
|||||||
s.dnsMuxMap = muxUpdateMap
|
s.dnsMuxMap = muxUpdateMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNSHostPort(ns nbdns.NameServer) string {
|
|
||||||
return formatAddr(ns.IP.String(), ns.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatAddr formats a nameserver address with port, handling IPv6 addresses properly
|
|
||||||
func formatAddr(address string, port int) string {
|
|
||||||
if ip, err := netip.ParseAddr(address); err == nil && ip.Is6() {
|
|
||||||
return fmt.Sprintf("[%s]:%d", address, port)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%d", address, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// upstreamCallbacks returns two functions, the first one is used to deactivate
|
// upstreamCallbacks returns two functions, the first one is used to deactivate
|
||||||
// the upstream resolver from the configuration, the second one is used to
|
// the upstream resolver from the configuration, the second one is used to
|
||||||
// reactivate it. Not allowed to call reactivate before deactivate.
|
// reactivate it. Not allowed to call reactivate before deactivate.
|
||||||
@@ -879,10 +873,7 @@ func (s *DefaultServer) addHostRootZone() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.upstreamServers = make([]string, 0)
|
handler.upstreamServers = maps.Keys(hostDNSServers)
|
||||||
for k := range hostDNSServers {
|
|
||||||
handler.upstreamServers = append(handler.upstreamServers, k)
|
|
||||||
}
|
|
||||||
handler.deactivate = func(error) {}
|
handler.deactivate = func(error) {}
|
||||||
handler.reactivate = func() {}
|
handler.reactivate = func() {}
|
||||||
|
|
||||||
@@ -893,9 +884,9 @@ func (s *DefaultServer) updateNSGroupStates(groups []*nbdns.NameServerGroup) {
|
|||||||
var states []peer.NSGroupState
|
var states []peer.NSGroupState
|
||||||
|
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
var servers []string
|
var servers []netip.AddrPort
|
||||||
for _, ns := range group.NameServers {
|
for _, ns := range group.NameServers {
|
||||||
servers = append(servers, fmt.Sprintf("%s:%d", ns.IP, ns.Port))
|
servers = append(servers, ns.AddrPort())
|
||||||
}
|
}
|
||||||
|
|
||||||
state := peer.NSGroupState{
|
state := peer.NSGroupState{
|
||||||
@@ -927,7 +918,7 @@ func (s *DefaultServer) updateNSState(nsGroup *nbdns.NameServerGroup, err error,
|
|||||||
func generateGroupKey(nsGroup *nbdns.NameServerGroup) string {
|
func generateGroupKey(nsGroup *nbdns.NameServerGroup) string {
|
||||||
var servers []string
|
var servers []string
|
||||||
for _, ns := range nsGroup.NameServers {
|
for _, ns := range nsGroup.NameServers {
|
||||||
servers = append(servers, fmt.Sprintf("%s:%d", ns.IP, ns.Port))
|
servers = append(servers, ns.AddrPort().String())
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v_%v", servers, nsGroup.Domains)
|
return fmt.Sprintf("%v_%v", servers, nsGroup.Domains)
|
||||||
}
|
}
|
||||||
|
|||||||
5
client/internal/dns/server_js.go
Normal file
5
client/internal/dns/server_js.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
func (s *DefaultServer) initialize() (hostManager, error) {
|
||||||
|
return &noopHostConfigurator{}, nil
|
||||||
|
}
|
||||||
@@ -97,9 +97,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateDummyHandler(domain string, servers []nbdns.NameServer) *upstreamResolverBase {
|
func generateDummyHandler(domain string, servers []nbdns.NameServer) *upstreamResolverBase {
|
||||||
var srvs []string
|
var srvs []netip.AddrPort
|
||||||
for _, srv := range servers {
|
for _, srv := range servers {
|
||||||
srvs = append(srvs, getNSHostPort(srv))
|
srvs = append(srvs, srv.AddrPort())
|
||||||
}
|
}
|
||||||
return &upstreamResolverBase{
|
return &upstreamResolverBase{
|
||||||
domain: domain,
|
domain: domain,
|
||||||
@@ -705,7 +705,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer wgIFace.Close()
|
defer wgIFace.Close()
|
||||||
|
|
||||||
var dnsList []string
|
var dnsList []netip.AddrPort
|
||||||
dnsConfig := nbdns.Config{}
|
dnsConfig := nbdns.Config{}
|
||||||
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil, peer.NewRecorder("mgm"), false)
|
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil, peer.NewRecorder("mgm"), false)
|
||||||
err = dnsServer.Initialize()
|
err = dnsServer.Initialize()
|
||||||
@@ -715,7 +715,8 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer dnsServer.Stop()
|
defer dnsServer.Stop()
|
||||||
|
|
||||||
dnsServer.OnUpdatedHostDNSServer([]string{"8.8.8.8"})
|
addrPort := netip.MustParseAddrPort("8.8.8.8:53")
|
||||||
|
dnsServer.OnUpdatedHostDNSServer([]netip.AddrPort{addrPort})
|
||||||
|
|
||||||
resolver := newDnsResolver(dnsServer.service.RuntimeIP(), dnsServer.service.RuntimePort())
|
resolver := newDnsResolver(dnsServer.service.RuntimeIP(), dnsServer.service.RuntimePort())
|
||||||
_, err = resolver.LookupHost(context.Background(), "netbird.io")
|
_, err = resolver.LookupHost(context.Background(), "netbird.io")
|
||||||
@@ -731,7 +732,8 @@ func TestDNSPermanent_updateUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer wgIFace.Close()
|
defer wgIFace.Close()
|
||||||
dnsConfig := nbdns.Config{}
|
dnsConfig := nbdns.Config{}
|
||||||
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, peer.NewRecorder("mgm"), false)
|
addrPort := netip.MustParseAddrPort("8.8.8.8:53")
|
||||||
|
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []netip.AddrPort{addrPort}, dnsConfig, nil, peer.NewRecorder("mgm"), false)
|
||||||
err = dnsServer.Initialize()
|
err = dnsServer.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to initialize DNS server: %v", err)
|
t.Errorf("failed to initialize DNS server: %v", err)
|
||||||
@@ -823,7 +825,8 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer wgIFace.Close()
|
defer wgIFace.Close()
|
||||||
dnsConfig := nbdns.Config{}
|
dnsConfig := nbdns.Config{}
|
||||||
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, peer.NewRecorder("mgm"), false)
|
addrPort := netip.MustParseAddrPort("8.8.8.8:53")
|
||||||
|
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []netip.AddrPort{addrPort}, dnsConfig, nil, peer.NewRecorder("mgm"), false)
|
||||||
err = dnsServer.Initialize()
|
err = dnsServer.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to initialize DNS server: %v", err)
|
t.Errorf("failed to initialize DNS server: %v", err)
|
||||||
@@ -2054,55 +2057,123 @@ func TestLocalResolverPriorityConstants(t *testing.T) {
|
|||||||
assert.Equal(t, "local.example.com", localMuxUpdates[0].domain)
|
assert.Equal(t, "local.example.com", localMuxUpdates[0].domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatAddr(t *testing.T) {
|
func TestDNSLoopPrevention(t *testing.T) {
|
||||||
|
wgInterface := &mocWGIface{}
|
||||||
|
service := NewServiceViaMemory(wgInterface)
|
||||||
|
dnsServerIP := service.RuntimeIP()
|
||||||
|
|
||||||
|
server := &DefaultServer{
|
||||||
|
ctx: context.Background(),
|
||||||
|
wgInterface: wgInterface,
|
||||||
|
service: service,
|
||||||
|
localResolver: local.NewResolver(),
|
||||||
|
handlerChain: NewHandlerChain(),
|
||||||
|
hostManager: &noopHostConfigurator{},
|
||||||
|
dnsMuxMap: make(registeredHandlerMap),
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
address string
|
nsGroups []*nbdns.NameServerGroup
|
||||||
port int
|
expectedHandlers int
|
||||||
expected string
|
expectedServers []netip.Addr
|
||||||
|
shouldFilterOwnIP bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "IPv4 address",
|
name: "FilterOwnDNSServerIP",
|
||||||
address: "8.8.8.8",
|
nsGroups: []*nbdns.NameServerGroup{
|
||||||
port: 53,
|
{
|
||||||
expected: "8.8.8.8:53",
|
Primary: true,
|
||||||
|
NameServers: []nbdns.NameServer{
|
||||||
|
{IP: netip.MustParseAddr("8.8.8.8"), NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
{IP: dnsServerIP, NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
{IP: netip.MustParseAddr("1.1.1.1"), NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
},
|
||||||
|
Domains: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedHandlers: 1,
|
||||||
|
expectedServers: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("1.1.1.1")},
|
||||||
|
shouldFilterOwnIP: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IPv4 address with custom port",
|
name: "AllServersFiltered",
|
||||||
address: "1.1.1.1",
|
nsGroups: []*nbdns.NameServerGroup{
|
||||||
port: 5353,
|
{
|
||||||
expected: "1.1.1.1:5353",
|
Primary: false,
|
||||||
|
NameServers: []nbdns.NameServer{
|
||||||
|
{IP: dnsServerIP, NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
},
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedHandlers: 0,
|
||||||
|
expectedServers: []netip.Addr{},
|
||||||
|
shouldFilterOwnIP: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IPv6 address",
|
name: "MixedServersWithOwnIP",
|
||||||
address: "fd78:94bf:7df8::1",
|
nsGroups: []*nbdns.NameServerGroup{
|
||||||
port: 53,
|
{
|
||||||
expected: "[fd78:94bf:7df8::1]:53",
|
Primary: false,
|
||||||
|
NameServers: []nbdns.NameServer{
|
||||||
|
{IP: netip.MustParseAddr("8.8.8.8"), NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
{IP: dnsServerIP, NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
{IP: netip.MustParseAddr("1.1.1.1"), NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
|
{IP: dnsServerIP, NSType: nbdns.UDPNameServerType, Port: 53}, // duplicate
|
||||||
|
},
|
||||||
|
Domains: []string{"test.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedHandlers: 1,
|
||||||
|
expectedServers: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("1.1.1.1")},
|
||||||
|
shouldFilterOwnIP: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IPv6 address with custom port",
|
name: "NoOwnIPInList",
|
||||||
address: "2001:db8::1",
|
nsGroups: []*nbdns.NameServerGroup{
|
||||||
port: 5353,
|
{
|
||||||
expected: "[2001:db8::1]:5353",
|
Primary: true,
|
||||||
},
|
NameServers: []nbdns.NameServer{
|
||||||
{
|
{IP: netip.MustParseAddr("8.8.8.8"), NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
name: "IPv6 localhost",
|
{IP: netip.MustParseAddr("1.1.1.1"), NSType: nbdns.UDPNameServerType, Port: 53},
|
||||||
address: "::1",
|
},
|
||||||
port: 53,
|
Domains: []string{},
|
||||||
expected: "[::1]:53",
|
},
|
||||||
},
|
},
|
||||||
{
|
expectedHandlers: 1,
|
||||||
name: "Invalid address treated as hostname",
|
expectedServers: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("1.1.1.1")},
|
||||||
address: "dns.example.com",
|
shouldFilterOwnIP: false,
|
||||||
port: 53,
|
|
||||||
expected: "dns.example.com:53",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := formatAddr(tt.address, tt.port)
|
muxUpdates, err := server.buildUpstreamHandlerUpdate(tt.nsGroups)
|
||||||
assert.Equal(t, tt.expected, result)
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, muxUpdates, tt.expectedHandlers)
|
||||||
|
|
||||||
|
if tt.expectedHandlers > 0 {
|
||||||
|
handler := muxUpdates[0].handler.(*upstreamResolver)
|
||||||
|
assert.Len(t, handler.upstreamServers, len(tt.expectedServers))
|
||||||
|
|
||||||
|
if tt.shouldFilterOwnIP {
|
||||||
|
for _, upstream := range handler.upstreamServers {
|
||||||
|
assert.NotEqual(t, dnsServerIP, upstream.Addr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expected := range tt.expectedServers {
|
||||||
|
found := false
|
||||||
|
for _, upstream := range handler.upstreamServers {
|
||||||
|
if upstream.Addr() == expected {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found, "Expected server %s not found", expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultPort = 53
|
DefaultPort = 53
|
||||||
)
|
)
|
||||||
|
|
||||||
type service interface {
|
type service interface {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func (s *serviceViaListener) RuntimePort() int {
|
|||||||
defer s.listenerFlagLock.Unlock()
|
defer s.listenerFlagLock.Unlock()
|
||||||
|
|
||||||
if s.ebpfService != nil {
|
if s.ebpfService != nil {
|
||||||
return defaultPort
|
return DefaultPort
|
||||||
} else {
|
} else {
|
||||||
return int(s.listenPort)
|
return int(s.listenPort)
|
||||||
}
|
}
|
||||||
@@ -148,9 +148,9 @@ func (s *serviceViaListener) evalListenAddress() (netip.Addr, uint16, error) {
|
|||||||
return s.customAddr.Addr(), s.customAddr.Port(), nil
|
return s.customAddr.Addr(), s.customAddr.Port(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, ok := s.testFreePort(defaultPort)
|
ip, ok := s.testFreePort(DefaultPort)
|
||||||
if ok {
|
if ok {
|
||||||
return ip, defaultPort, nil
|
return ip, DefaultPort, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ebpfSrv, port, ok := s.tryToUseeBPF()
|
ebpfSrv, port, ok := s.tryToUseeBPF()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func NewServiceViaMemory(wgIface WGIface) *ServiceViaMemory {
|
|||||||
dnsMux: dns.NewServeMux(),
|
dnsMux: dns.NewServeMux(),
|
||||||
|
|
||||||
runtimeIP: lastIP,
|
runtimeIP: lastIP,
|
||||||
runtimePort: defaultPort,
|
runtimePort: DefaultPort,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ func (s *systemdDbusConfigurator) callLinkMethod(method string, value any) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemdDbusConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
|
func (s *systemdDbusConfigurator) restoreUncleanShutdownDNS(netip.Addr) error {
|
||||||
if err := s.restoreHostDNS(); err != nil {
|
if err := s.restoreHostDNS(); err != nil {
|
||||||
return fmt.Errorf("restoring dns via systemd: %w", err)
|
return fmt.Errorf("restoring dns via systemd: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
19
client/internal/dns/unclean_shutdown_js.go
Normal file
19
client/internal/dns/unclean_shutdown_js.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShutdownState struct{}
|
||||||
|
|
||||||
|
func (s *ShutdownState) Name() string {
|
||||||
|
return "dns_state"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShutdownState) Cleanup() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShutdownState) RestoreUncleanShutdownConfigs(context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ func (s *ShutdownState) Cleanup() error {
|
|||||||
return fmt.Errorf("create previous host manager: %w", err)
|
return fmt.Errorf("create previous host manager: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.restoreUncleanShutdownDNS(&s.DNSAddress); err != nil {
|
if err := manager.restoreUncleanShutdownDNS(s.DNSAddress); err != nil {
|
||||||
return fmt.Errorf("restore unclean shutdown dns: %w", err)
|
return fmt.Errorf("restore unclean shutdown dns: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ShutdownState struct {
|
type ShutdownState struct {
|
||||||
Guid string
|
Guid string
|
||||||
GPO bool
|
GPO bool
|
||||||
|
NRPTEntryCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShutdownState) Name() string {
|
func (s *ShutdownState) Name() string {
|
||||||
@@ -15,8 +16,9 @@ func (s *ShutdownState) Name() string {
|
|||||||
|
|
||||||
func (s *ShutdownState) Cleanup() error {
|
func (s *ShutdownState) Cleanup() error {
|
||||||
manager := ®istryConfigurator{
|
manager := ®istryConfigurator{
|
||||||
guid: s.Guid,
|
guid: s.Guid,
|
||||||
gpo: s.GPO,
|
gpo: s.GPO,
|
||||||
|
nrptEntryCount: s.NRPTEntryCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.restoreUncleanShutdownDNS(); err != nil {
|
if err := manager.restoreUncleanShutdownDNS(); err != nil {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -48,7 +49,7 @@ type upstreamResolverBase struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
upstreamClient upstreamClient
|
upstreamClient upstreamClient
|
||||||
upstreamServers []string
|
upstreamServers []netip.AddrPort
|
||||||
domain string
|
domain string
|
||||||
disabled bool
|
disabled bool
|
||||||
failsCount atomic.Int32
|
failsCount atomic.Int32
|
||||||
@@ -79,17 +80,20 @@ func newUpstreamResolverBase(ctx context.Context, statusRecorder *peer.Status, d
|
|||||||
|
|
||||||
// String returns a string representation of the upstream resolver
|
// String returns a string representation of the upstream resolver
|
||||||
func (u *upstreamResolverBase) String() string {
|
func (u *upstreamResolverBase) String() string {
|
||||||
return fmt.Sprintf("upstream %v", u.upstreamServers)
|
return fmt.Sprintf("upstream %s", u.upstreamServers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the unique handler ID
|
// ID returns the unique handler ID
|
||||||
func (u *upstreamResolverBase) ID() types.HandlerID {
|
func (u *upstreamResolverBase) ID() types.HandlerID {
|
||||||
servers := slices.Clone(u.upstreamServers)
|
servers := slices.Clone(u.upstreamServers)
|
||||||
slices.Sort(servers)
|
slices.SortFunc(servers, func(a, b netip.AddrPort) int { return a.Compare(b) })
|
||||||
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
hash.Write([]byte(u.domain + ":"))
|
hash.Write([]byte(u.domain + ":"))
|
||||||
hash.Write([]byte(strings.Join(servers, ",")))
|
for _, s := range servers {
|
||||||
|
hash.Write([]byte(s.String()))
|
||||||
|
hash.Write([]byte("|"))
|
||||||
|
}
|
||||||
return types.HandlerID("upstream-" + hex.EncodeToString(hash.Sum(nil)[:8]))
|
return types.HandlerID("upstream-" + hex.EncodeToString(hash.Sum(nil)[:8]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +134,7 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
func() {
|
func() {
|
||||||
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
rm, t, err = u.upstreamClient.exchange(ctx, upstream, r)
|
rm, t, err = u.upstreamClient.exchange(ctx, upstream.String(), r)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -197,7 +201,7 @@ func (u *upstreamResolverBase) checkUpstreamFails(err error) {
|
|||||||
proto.SystemEvent_DNS,
|
proto.SystemEvent_DNS,
|
||||||
"All upstream servers failed (fail count exceeded)",
|
"All upstream servers failed (fail count exceeded)",
|
||||||
"Unable to reach one or more DNS servers. This might affect your ability to connect to some services.",
|
"Unable to reach one or more DNS servers. This might affect your ability to connect to some services.",
|
||||||
map[string]string{"upstreams": strings.Join(u.upstreamServers, ", ")},
|
map[string]string{"upstreams": u.upstreamServersString()},
|
||||||
// TODO add domain meta
|
// TODO add domain meta
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -258,7 +262,7 @@ func (u *upstreamResolverBase) ProbeAvailability() {
|
|||||||
proto.SystemEvent_DNS,
|
proto.SystemEvent_DNS,
|
||||||
"All upstream servers failed (probe failed)",
|
"All upstream servers failed (probe failed)",
|
||||||
"Unable to reach one or more DNS servers. This might affect your ability to connect to some services.",
|
"Unable to reach one or more DNS servers. This might affect your ability to connect to some services.",
|
||||||
map[string]string{"upstreams": strings.Join(u.upstreamServers, ", ")},
|
map[string]string{"upstreams": u.upstreamServersString()},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +282,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
|
|||||||
operation := func() error {
|
operation := func() error {
|
||||||
select {
|
select {
|
||||||
case <-u.ctx.Done():
|
case <-u.ctx.Done():
|
||||||
return backoff.Permanent(fmt.Errorf("exiting upstream retry loop for upstreams %s: parent context has been canceled", u.upstreamServers))
|
return backoff.Permanent(fmt.Errorf("exiting upstream retry loop for upstreams %s: parent context has been canceled", u.upstreamServersString()))
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +295,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracef("checking connectivity with upstreams %s failed. Retrying in %s", u.upstreamServers, exponentialBackOff.NextBackOff())
|
log.Tracef("checking connectivity with upstreams %s failed. Retrying in %s", u.upstreamServersString(), exponentialBackOff.NextBackOff())
|
||||||
return fmt.Errorf("upstream check call error")
|
return fmt.Errorf("upstream check call error")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +305,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("upstreams %s are responsive again. Adding them back to system", u.upstreamServers)
|
log.Infof("upstreams %s are responsive again. Adding them back to system", u.upstreamServersString())
|
||||||
u.failsCount.Store(0)
|
u.failsCount.Store(0)
|
||||||
u.successCount.Add(1)
|
u.successCount.Add(1)
|
||||||
u.reactivate()
|
u.reactivate()
|
||||||
@@ -331,13 +335,21 @@ func (u *upstreamResolverBase) disable(err error) {
|
|||||||
go u.waitUntilResponse()
|
go u.waitUntilResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *upstreamResolverBase) testNameserver(server string, timeout time.Duration) error {
|
func (u *upstreamResolverBase) upstreamServersString() string {
|
||||||
|
var servers []string
|
||||||
|
for _, server := range u.upstreamServers {
|
||||||
|
servers = append(servers, server.String())
|
||||||
|
}
|
||||||
|
return strings.Join(servers, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *upstreamResolverBase) testNameserver(server netip.AddrPort, timeout time.Duration) error {
|
||||||
ctx, cancel := context.WithTimeout(u.ctx, timeout)
|
ctx, cancel := context.WithTimeout(u.ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
r := new(dns.Msg).SetQuestion(testRecord, dns.TypeSOA)
|
r := new(dns.Msg).SetQuestion(testRecord, dns.TypeSOA)
|
||||||
|
|
||||||
_, _, err := u.upstreamClient.exchange(ctx, server, r)
|
_, _, err := u.upstreamClient.exchange(ctx, server.String(), r)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ func (u *upstreamResolver) exchangeWithoutVPN(ctx context.Context, upstream stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *upstreamResolver) isLocalResolver(upstream string) bool {
|
func (u *upstreamResolver) isLocalResolver(upstream string) bool {
|
||||||
if u.hostsDNSHolder.isContain(upstream) {
|
if addrPort, err := netip.ParseAddrPort(upstream); err == nil {
|
||||||
return true
|
return u.hostsDNSHolder.contains(addrPort)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
|
|||||||
upstreamIP, err := netip.ParseAddr(upstreamHost)
|
upstreamIP, err := netip.ParseAddr(upstreamHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to parse upstream host %s: %s", upstreamHost, err)
|
log.Warnf("failed to parse upstream host %s: %s", upstreamHost, err)
|
||||||
|
} else {
|
||||||
|
upstreamIP = upstreamIP.Unmap()
|
||||||
}
|
}
|
||||||
if u.lNet.Contains(upstreamIP) || upstreamIP.IsPrivate() {
|
if u.lNet.Contains(upstreamIP) || upstreamIP.IsPrivate() {
|
||||||
log.Debugf("using private client to query upstream: %s", upstream)
|
log.Debugf("using private client to query upstream: %s", upstream)
|
||||||
|
|||||||
@@ -59,7 +59,14 @@ func TestUpstreamResolver_ServeDNS(t *testing.T) {
|
|||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
resolver, _ := newUpstreamResolver(ctx, "", netip.Addr{}, netip.Prefix{}, nil, nil, ".")
|
resolver, _ := newUpstreamResolver(ctx, "", netip.Addr{}, netip.Prefix{}, nil, nil, ".")
|
||||||
resolver.upstreamServers = testCase.InputServers
|
// Convert test servers to netip.AddrPort
|
||||||
|
var servers []netip.AddrPort
|
||||||
|
for _, server := range testCase.InputServers {
|
||||||
|
if addrPort, err := netip.ParseAddrPort(server); err == nil {
|
||||||
|
servers = append(servers, netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolver.upstreamServers = servers
|
||||||
resolver.upstreamTimeout = testCase.timeout
|
resolver.upstreamTimeout = testCase.timeout
|
||||||
if testCase.cancelCTX {
|
if testCase.cancelCTX {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -128,7 +135,8 @@ func TestUpstreamResolver_DeactivationReactivation(t *testing.T) {
|
|||||||
reactivatePeriod: reactivatePeriod,
|
reactivatePeriod: reactivatePeriod,
|
||||||
failsTillDeact: failsTillDeact,
|
failsTillDeact: failsTillDeact,
|
||||||
}
|
}
|
||||||
resolver.upstreamServers = []string{"0.0.0.0:-1"}
|
addrPort, _ := netip.ParseAddrPort("0.0.0.0:1") // Use valid port for parsing, test will still fail on connection
|
||||||
|
resolver.upstreamServers = []netip.AddrPort{netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())}
|
||||||
resolver.failsTillDeact = 0
|
resolver.failsTillDeact = 0
|
||||||
resolver.reactivatePeriod = time.Microsecond * 100
|
resolver.reactivatePeriod = time.Microsecond * 100
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func (f *DNSForwarder) handleDNSQuery(w dns.ResponseWriter, query *dns.Msg) *dns
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
ips, err := f.resolver.LookupNetIP(ctx, network, domain)
|
ips, err := f.resolver.LookupNetIP(ctx, network, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.handleDNSError(w, query, resp, domain, err)
|
f.handleDNSError(ctx, w, question, resp, domain, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,20 +244,57 @@ func (f *DNSForwarder) updateFirewall(matchingEntries []*ForwarderEntry, prefixe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setResponseCodeForNotFound determines and sets the appropriate response code when IsNotFound is true
|
||||||
|
// It distinguishes between NXDOMAIN (domain doesn't exist) and NODATA (domain exists but no records of requested type)
|
||||||
|
//
|
||||||
|
// LIMITATION: This function only checks A and AAAA record types to determine domain existence.
|
||||||
|
// If a domain has only other record types (MX, TXT, CNAME, etc.) but no A/AAAA records,
|
||||||
|
// it may incorrectly return NXDOMAIN instead of NODATA. This is acceptable since the forwarder
|
||||||
|
// only handles A/AAAA queries and returns NOTIMP for other types.
|
||||||
|
func (f *DNSForwarder) setResponseCodeForNotFound(ctx context.Context, resp *dns.Msg, domain string, originalQtype uint16) {
|
||||||
|
// Try querying for a different record type to see if the domain exists
|
||||||
|
// If the original query was for AAAA, try A. If it was for A, try AAAA.
|
||||||
|
// This helps distinguish between NXDOMAIN and NODATA.
|
||||||
|
var alternativeNetwork string
|
||||||
|
switch originalQtype {
|
||||||
|
case dns.TypeAAAA:
|
||||||
|
alternativeNetwork = "ip4"
|
||||||
|
case dns.TypeA:
|
||||||
|
alternativeNetwork = "ip6"
|
||||||
|
default:
|
||||||
|
resp.Rcode = dns.RcodeNameError
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := f.resolver.LookupNetIP(ctx, alternativeNetwork, domain); err != nil {
|
||||||
|
var dnsErr *net.DNSError
|
||||||
|
if errors.As(err, &dnsErr) && dnsErr.IsNotFound {
|
||||||
|
// Alternative query also returned not found - domain truly doesn't exist
|
||||||
|
resp.Rcode = dns.RcodeNameError
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Some other error (timeout, server failure, etc.) - can't determine, assume domain exists
|
||||||
|
resp.Rcode = dns.RcodeSuccess
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternative query succeeded - domain exists but has no records of this type
|
||||||
|
resp.Rcode = dns.RcodeSuccess
|
||||||
|
}
|
||||||
|
|
||||||
// handleDNSError processes DNS lookup errors and sends an appropriate error response
|
// handleDNSError processes DNS lookup errors and sends an appropriate error response
|
||||||
func (f *DNSForwarder) handleDNSError(w dns.ResponseWriter, query, resp *dns.Msg, domain string, err error) {
|
func (f *DNSForwarder) handleDNSError(ctx context.Context, w dns.ResponseWriter, question dns.Question, resp *dns.Msg, domain string, err error) {
|
||||||
var dnsErr *net.DNSError
|
var dnsErr *net.DNSError
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errors.As(err, &dnsErr):
|
case errors.As(err, &dnsErr):
|
||||||
resp.Rcode = dns.RcodeServerFailure
|
resp.Rcode = dns.RcodeServerFailure
|
||||||
if dnsErr.IsNotFound {
|
if dnsErr.IsNotFound {
|
||||||
// Pass through NXDOMAIN
|
f.setResponseCodeForNotFound(ctx, resp, domain, question.Qtype)
|
||||||
resp.Rcode = dns.RcodeNameError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dnsErr.Server != "" {
|
if dnsErr.Server != "" {
|
||||||
log.Warnf("failed to resolve query for type=%s domain=%s server=%s: %v", dns.TypeToString[query.Question[0].Qtype], domain, dnsErr.Server, err)
|
log.Warnf("failed to resolve query for type=%s domain=%s server=%s: %v", dns.TypeToString[question.Qtype], domain, dnsErr.Server, err)
|
||||||
} else {
|
} else {
|
||||||
log.Warnf(errResolveFailed, domain, err)
|
log.Warnf(errResolveFailed, domain, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dnsfwd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -16,8 +17,8 @@ import (
|
|||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns/test"
|
"github.com/netbirdio/netbird/client/internal/dns/test"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_getMatchingEntries(t *testing.T) {
|
func Test_getMatchingEntries(t *testing.T) {
|
||||||
@@ -708,6 +709,131 @@ func TestDNSForwarder_MultipleOverlappingPatterns(t *testing.T) {
|
|||||||
assert.Len(t, matches, 3, "Should match 3 patterns")
|
assert.Len(t, matches, 3, "Should match 3 patterns")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDNSForwarder_NodataVsNxdomain tests that the forwarder correctly distinguishes
|
||||||
|
// between NXDOMAIN (domain doesn't exist) and NODATA (domain exists but no records of that type)
|
||||||
|
func TestDNSForwarder_NodataVsNxdomain(t *testing.T) {
|
||||||
|
mockFirewall := &MockFirewall{}
|
||||||
|
mockResolver := &MockResolver{}
|
||||||
|
|
||||||
|
forwarder := NewDNSForwarder("127.0.0.1:0", 300, mockFirewall, &peer.Status{})
|
||||||
|
forwarder.resolver = mockResolver
|
||||||
|
|
||||||
|
d, err := domain.FromString("example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
set := firewall.NewDomainSet([]domain.Domain{d})
|
||||||
|
entries := []*ForwarderEntry{{Domain: d, ResID: "test-res", Set: set}}
|
||||||
|
forwarder.UpdateDomains(entries)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
queryType uint16
|
||||||
|
setupMocks func()
|
||||||
|
expectedCode int
|
||||||
|
expectNoAnswer bool // true if we expect NOERROR with empty answer (NODATA case)
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "domain exists but no AAAA records (NODATA)",
|
||||||
|
queryType: dns.TypeAAAA,
|
||||||
|
setupMocks: func() {
|
||||||
|
// First query for AAAA returns not found
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip6", "example.com.").
|
||||||
|
Return([]netip.Addr{}, &net.DNSError{IsNotFound: true, Name: "example.com"}).Once()
|
||||||
|
// Check query for A records succeeds (domain exists)
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip4", "example.com.").
|
||||||
|
Return([]netip.Addr{netip.MustParseAddr("1.2.3.4")}, nil).Once()
|
||||||
|
},
|
||||||
|
expectedCode: dns.RcodeSuccess,
|
||||||
|
expectNoAnswer: true,
|
||||||
|
description: "Should return NOERROR when domain exists but has no records of requested type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain exists but no A records (NODATA)",
|
||||||
|
queryType: dns.TypeA,
|
||||||
|
setupMocks: func() {
|
||||||
|
// First query for A returns not found
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip4", "example.com.").
|
||||||
|
Return([]netip.Addr{}, &net.DNSError{IsNotFound: true, Name: "example.com"}).Once()
|
||||||
|
// Check query for AAAA records succeeds (domain exists)
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip6", "example.com.").
|
||||||
|
Return([]netip.Addr{netip.MustParseAddr("2001:db8::1")}, nil).Once()
|
||||||
|
},
|
||||||
|
expectedCode: dns.RcodeSuccess,
|
||||||
|
expectNoAnswer: true,
|
||||||
|
description: "Should return NOERROR when domain exists but has no A records",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain doesn't exist (NXDOMAIN)",
|
||||||
|
queryType: dns.TypeA,
|
||||||
|
setupMocks: func() {
|
||||||
|
// First query for A returns not found
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip4", "example.com.").
|
||||||
|
Return([]netip.Addr{}, &net.DNSError{IsNotFound: true, Name: "example.com"}).Once()
|
||||||
|
// Check query for AAAA also returns not found (domain doesn't exist)
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip6", "example.com.").
|
||||||
|
Return([]netip.Addr{}, &net.DNSError{IsNotFound: true, Name: "example.com"}).Once()
|
||||||
|
},
|
||||||
|
expectedCode: dns.RcodeNameError,
|
||||||
|
expectNoAnswer: true,
|
||||||
|
description: "Should return NXDOMAIN when domain doesn't exist at all",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain exists with records (normal success)",
|
||||||
|
queryType: dns.TypeA,
|
||||||
|
setupMocks: func() {
|
||||||
|
mockResolver.On("LookupNetIP", mock.Anything, "ip4", "example.com.").
|
||||||
|
Return([]netip.Addr{netip.MustParseAddr("1.2.3.4")}, nil).Once()
|
||||||
|
// Expect firewall update for successful resolution
|
||||||
|
expectedPrefix := netip.PrefixFrom(netip.MustParseAddr("1.2.3.4"), 32)
|
||||||
|
mockFirewall.On("UpdateSet", set, []netip.Prefix{expectedPrefix}).Return(nil).Once()
|
||||||
|
},
|
||||||
|
expectedCode: dns.RcodeSuccess,
|
||||||
|
expectNoAnswer: false,
|
||||||
|
description: "Should return NOERROR with answer when records exist",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Reset mock expectations
|
||||||
|
mockResolver.ExpectedCalls = nil
|
||||||
|
mockResolver.Calls = nil
|
||||||
|
mockFirewall.ExpectedCalls = nil
|
||||||
|
mockFirewall.Calls = nil
|
||||||
|
|
||||||
|
tt.setupMocks()
|
||||||
|
|
||||||
|
query := &dns.Msg{}
|
||||||
|
query.SetQuestion(dns.Fqdn("example.com"), tt.queryType)
|
||||||
|
|
||||||
|
var writtenResp *dns.Msg
|
||||||
|
mockWriter := &test.MockResponseWriter{
|
||||||
|
WriteMsgFunc: func(m *dns.Msg) error {
|
||||||
|
writtenResp = m
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := forwarder.handleDNSQuery(mockWriter, query)
|
||||||
|
|
||||||
|
// If a response was returned, it means it should be written (happens in wrapper functions)
|
||||||
|
if resp != nil && writtenResp == nil {
|
||||||
|
writtenResp = resp
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotNil(t, writtenResp, "Expected response to be written")
|
||||||
|
assert.Equal(t, tt.expectedCode, writtenResp.Rcode, tt.description)
|
||||||
|
|
||||||
|
if tt.expectNoAnswer {
|
||||||
|
assert.Empty(t, writtenResp.Answer, "Response should have no answer records")
|
||||||
|
}
|
||||||
|
|
||||||
|
mockResolver.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDNSForwarder_EmptyQuery(t *testing.T) {
|
func TestDNSForwarder_EmptyQuery(t *testing.T) {
|
||||||
// Test handling of malformed query with no questions
|
// Test handling of malformed query with no questions
|
||||||
forwarder := NewDNSForwarder("127.0.0.1:0", 300, nil, &peer.Status{})
|
forwarder := NewDNSForwarder("127.0.0.1:0", 300, nil, &peer.Status{})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -11,14 +12,18 @@ import (
|
|||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ListenPort is the port that the DNS forwarder listens on. It has been used by the client peers also
|
||||||
|
listenPort uint16 = 5353
|
||||||
|
listenPortMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ListenPort is the port that the DNS forwarder listens on. It has been used by the client peers also
|
dnsTTL = 60 //seconds
|
||||||
ListenPort = 5353
|
|
||||||
dnsTTL = 60 //seconds
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ForwarderEntry is a mapping from a domain to a resource ID and a hash of the parent domain list.
|
// ForwarderEntry is a mapping from a domain to a resource ID and a hash of the parent domain list.
|
||||||
@@ -35,12 +40,20 @@ type Manager struct {
|
|||||||
fwRules []firewall.Rule
|
fwRules []firewall.Rule
|
||||||
tcpRules []firewall.Rule
|
tcpRules []firewall.Rule
|
||||||
dnsForwarder *DNSForwarder
|
dnsForwarder *DNSForwarder
|
||||||
|
port uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(fw firewall.Manager, statusRecorder *peer.Status) *Manager {
|
func ListenPort() uint16 {
|
||||||
|
listenPortMu.RLock()
|
||||||
|
defer listenPortMu.RUnlock()
|
||||||
|
return listenPort
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(fw firewall.Manager, statusRecorder *peer.Status, port uint16) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
firewall: fw,
|
firewall: fw,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
|
port: port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +67,13 @@ func (m *Manager) Start(fwdEntries []*ForwarderEntry) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.dnsForwarder = NewDNSForwarder(fmt.Sprintf(":%d", ListenPort), dnsTTL, m.firewall, m.statusRecorder)
|
if m.port > 0 {
|
||||||
|
listenPortMu.Lock()
|
||||||
|
listenPort = m.port
|
||||||
|
listenPortMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.dnsForwarder = NewDNSForwarder(fmt.Sprintf(":%d", ListenPort()), dnsTTL, m.firewall, m.statusRecorder)
|
||||||
go func() {
|
go func() {
|
||||||
if err := m.dnsForwarder.Listen(fwdEntries); err != nil {
|
if err := m.dnsForwarder.Listen(fwdEntries); err != nil {
|
||||||
// todo handle close error if it is exists
|
// todo handle close error if it is exists
|
||||||
@@ -94,7 +113,7 @@ func (m *Manager) Stop(ctx context.Context) error {
|
|||||||
func (m *Manager) allowDNSFirewall() error {
|
func (m *Manager) allowDNSFirewall() error {
|
||||||
dport := &firewall.Port{
|
dport := &firewall.Port{
|
||||||
IsRange: false,
|
IsRange: false,
|
||||||
Values: []uint16{ListenPort},
|
Values: []uint16{ListenPort()},
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.firewall == nil {
|
if m.firewall == nil {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
"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"
|
||||||
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
||||||
"github.com/netbirdio/netbird/client/internal/ingressgw"
|
"github.com/netbirdio/netbird/client/internal/ingressgw"
|
||||||
@@ -48,21 +49,24 @@ 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"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
|
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
|
||||||
|
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
mgm "github.com/netbirdio/netbird/shared/management/client"
|
mgm "github.com/netbirdio/netbird/shared/management/client"
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
auth "github.com/netbirdio/netbird/shared/relay/auth/hmac"
|
auth "github.com/netbirdio/netbird/shared/relay/auth/hmac"
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
"github.com/netbirdio/netbird/route"
|
|
||||||
signal "github.com/netbirdio/netbird/shared/signal/client"
|
signal "github.com/netbirdio/netbird/shared/signal/client"
|
||||||
sProto "github.com/netbirdio/netbird/shared/signal/proto"
|
sProto "github.com/netbirdio/netbird/shared/signal/proto"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
|
// PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
|
||||||
@@ -125,6 +129,11 @@ type EngineConfig struct {
|
|||||||
BlockInbound bool
|
BlockInbound bool
|
||||||
|
|
||||||
LazyConnectionEnabled bool
|
LazyConnectionEnabled bool
|
||||||
|
|
||||||
|
// for debug bundle generation
|
||||||
|
ProfileConfig *profilemanager.Config
|
||||||
|
|
||||||
|
LogFile 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.
|
||||||
@@ -189,11 +198,18 @@ 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
|
||||||
flowManager nftypes.FlowManager
|
flowManager nftypes.FlowManager
|
||||||
|
|
||||||
|
jobExecutor *jobexec.Executor
|
||||||
|
jobExecutorWG sync.WaitGroup
|
||||||
|
|
||||||
|
// dns forwarder port
|
||||||
|
dnsFwdPort uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -207,17 +223,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,
|
||||||
@@ -236,6 +242,8 @@ func NewEngine(
|
|||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
checks: checks,
|
checks: checks,
|
||||||
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
|
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
|
||||||
|
jobExecutor: jobexec.NewExecutor(),
|
||||||
|
dnsFwdPort: dnsfwd.ListenPort(),
|
||||||
}
|
}
|
||||||
|
|
||||||
sm := profilemanager.NewServiceManager("")
|
sm := profilemanager.NewServiceManager("")
|
||||||
@@ -254,6 +262,7 @@ func NewEngine(
|
|||||||
}
|
}
|
||||||
engine.stateManager = statemanager.New(path)
|
engine.stateManager = statemanager.New(path)
|
||||||
|
|
||||||
|
log.Infof("I am: %s", config.WgPrivateKey.PublicKey().String())
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +322,8 @@ func (e *Engine) Stop() error {
|
|||||||
e.cancel()
|
e.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.jobExecutorWG.Wait() // block until job goroutines finish
|
||||||
|
|
||||||
// very ugly but we want to remove peers from the WireGuard interface first before removing interface.
|
// very ugly but we want to remove peers from the WireGuard interface first before removing interface.
|
||||||
// Removing peers happens in the conn.Close() asynchronously
|
// Removing peers happens in the conn.Close() asynchronously
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
@@ -443,14 +454,7 @@ func (e *Engine) Start() error {
|
|||||||
return fmt.Errorf("initialize dns server: %w", err)
|
return fmt.Errorf("initialize dns server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iceCfg := icemaker.Config{
|
iceCfg := e.createICEConfig()
|
||||||
StunTurn: &e.stunTurn,
|
|
||||||
InterfaceBlackList: e.config.IFaceBlackList,
|
|
||||||
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
|
||||||
UDPMux: e.udpMux.UDPMuxDefault,
|
|
||||||
UDPMuxSrflx: e.udpMux,
|
|
||||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
|
||||||
}
|
|
||||||
|
|
||||||
e.connMgr = NewConnMgr(e.config, e.statusRecorder, e.peerStore, wgIface)
|
e.connMgr = NewConnMgr(e.config, e.statusRecorder, e.peerStore, wgIface)
|
||||||
e.connMgr.Start(e.ctx)
|
e.connMgr.Start(e.ctx)
|
||||||
@@ -460,6 +464,7 @@ func (e *Engine) Start() error {
|
|||||||
|
|
||||||
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()
|
||||||
@@ -697,9 +702,18 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
|||||||
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,6 +898,83 @@ 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 {
|
||||||
|
resp.Reason = []byte(err.Error())
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
resp.Status = mgmProto.JobStatus_succeeded
|
||||||
|
resp.WorkloadResults = bundleResult
|
||||||
|
return &resp
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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.Debugf("stopped receiving jobs from Management Service")
|
||||||
|
}()
|
||||||
|
log.Debugf("connecting to Management Service jobs stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) handleBundle(params *mgmProto.BundleParameters) (*mgmProto.JobResponse_Bundle, error) {
|
||||||
|
syncResponse, err := e.GetLatestSyncResponse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get latest sync response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if syncResponse == nil {
|
||||||
|
return nil, errors.New("sync response is not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert fullStatus to statusOutput
|
||||||
|
fullStatus := e.statusRecorder.GetFullStatus()
|
||||||
|
protoFullStatus := nbstatus.ToProtoFullStatus(fullStatus)
|
||||||
|
overview := nbstatus.ConvertToStatusOutputOverview(protoFullStatus, params.Anonymize, version.NetbirdVersion(), "", nil, nil, nil, "", "")
|
||||||
|
statusOutput := nbstatus.ParseToFullDetailSummary(overview)
|
||||||
|
|
||||||
|
bundleDeps := debug.GeneratorDependencies{
|
||||||
|
InternalConfig: e.config.ProfileConfig,
|
||||||
|
StatusRecorder: e.statusRecorder,
|
||||||
|
SyncResponse: syncResponse,
|
||||||
|
LogFile: e.config.LogFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleJobParams := debug.BundleConfig{
|
||||||
|
Anonymize: params.Anonymize,
|
||||||
|
ClientStatus: statusOutput,
|
||||||
|
IncludeSystemInfo: true,
|
||||||
|
LogFileCount: uint32(params.LogFileCount),
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadKey, err := e.jobExecutor.BundleJob(e.ctx, bundleDeps, bundleJobParams, 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.
|
||||||
@@ -1023,7 +1114,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fwdEntries := toRouteDomains(e.config.WgPrivateKey.PublicKey().String(), routes)
|
fwdEntries := toRouteDomains(e.config.WgPrivateKey.PublicKey().String(), routes)
|
||||||
e.updateDNSForwarder(dnsRouteFeatureFlag, fwdEntries)
|
e.updateDNSForwarder(dnsRouteFeatureFlag, fwdEntries, uint16(protoDNSConfig.ForwarderPort))
|
||||||
|
|
||||||
// Ingress forward rules
|
// Ingress forward rules
|
||||||
forwardingRules, err := e.updateForwardRules(networkMap.GetForwardingRules())
|
forwardingRules, err := e.updateForwardRules(networkMap.GetForwardingRules())
|
||||||
@@ -1280,14 +1371,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV
|
|||||||
Addr: e.getRosenpassAddr(),
|
Addr: e.getRosenpassAddr(),
|
||||||
PermissiveMode: e.config.RosenpassPermissive,
|
PermissiveMode: e.config.RosenpassPermissive,
|
||||||
},
|
},
|
||||||
ICEConfig: icemaker.Config{
|
ICEConfig: e.createICEConfig(),
|
||||||
StunTurn: &e.stunTurn,
|
|
||||||
InterfaceBlackList: e.config.IFaceBlackList,
|
|
||||||
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
|
||||||
UDPMux: e.udpMux.UDPMuxDefault,
|
|
||||||
UDPMuxSrflx: e.udpMux,
|
|
||||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceDependencies := peer.ServiceDependencies{
|
serviceDependencies := peer.ServiceDependencies{
|
||||||
@@ -1330,52 +1414,17 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch msg.GetBody().Type {
|
switch msg.GetBody().Type {
|
||||||
case sProto.Body_OFFER:
|
case sProto.Body_OFFER, sProto.Body_ANSWER:
|
||||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
offerAnswer, err := convertToOfferAnswer(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rosenpassPubKey []byte
|
if msg.Body.Type == sProto.Body_OFFER {
|
||||||
rosenpassAddr := ""
|
conn.OnRemoteOffer(*offerAnswer)
|
||||||
if msg.GetBody().GetRosenpassConfig() != nil {
|
} else {
|
||||||
rosenpassPubKey = msg.GetBody().GetRosenpassConfig().GetRosenpassPubKey()
|
conn.OnRemoteAnswer(*offerAnswer)
|
||||||
rosenpassAddr = msg.GetBody().GetRosenpassConfig().GetRosenpassServerAddr()
|
|
||||||
}
|
}
|
||||||
conn.OnRemoteOffer(peer.OfferAnswer{
|
|
||||||
IceCredentials: peer.IceCredentials{
|
|
||||||
UFrag: remoteCred.UFrag,
|
|
||||||
Pwd: remoteCred.Pwd,
|
|
||||||
},
|
|
||||||
WgListenPort: int(msg.GetBody().GetWgListenPort()),
|
|
||||||
Version: msg.GetBody().GetNetBirdVersion(),
|
|
||||||
RosenpassPubKey: rosenpassPubKey,
|
|
||||||
RosenpassAddr: rosenpassAddr,
|
|
||||||
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
|
|
||||||
})
|
|
||||||
case sProto.Body_ANSWER:
|
|
||||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rosenpassPubKey []byte
|
|
||||||
rosenpassAddr := ""
|
|
||||||
if msg.GetBody().GetRosenpassConfig() != nil {
|
|
||||||
rosenpassPubKey = msg.GetBody().GetRosenpassConfig().GetRosenpassPubKey()
|
|
||||||
rosenpassAddr = msg.GetBody().GetRosenpassConfig().GetRosenpassServerAddr()
|
|
||||||
}
|
|
||||||
conn.OnRemoteAnswer(peer.OfferAnswer{
|
|
||||||
IceCredentials: peer.IceCredentials{
|
|
||||||
UFrag: remoteCred.UFrag,
|
|
||||||
Pwd: remoteCred.Pwd,
|
|
||||||
},
|
|
||||||
WgListenPort: int(msg.GetBody().GetWgListenPort()),
|
|
||||||
Version: msg.GetBody().GetNetBirdVersion(),
|
|
||||||
RosenpassPubKey: rosenpassPubKey,
|
|
||||||
RosenpassAddr: rosenpassAddr,
|
|
||||||
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
|
|
||||||
})
|
|
||||||
case sProto.Body_CANDIDATE:
|
case sProto.Body_CANDIDATE:
|
||||||
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1767,8 +1816,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
|
||||||
@@ -1783,20 +1832,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")
|
||||||
}
|
}
|
||||||
@@ -1816,6 +1867,7 @@ func (e *Engine) GetWgAddr() netip.Addr {
|
|||||||
func (e *Engine) updateDNSForwarder(
|
func (e *Engine) updateDNSForwarder(
|
||||||
enabled bool,
|
enabled bool,
|
||||||
fwdEntries []*dnsfwd.ForwarderEntry,
|
fwdEntries []*dnsfwd.ForwarderEntry,
|
||||||
|
forwarderPort uint16,
|
||||||
) {
|
) {
|
||||||
if e.config.DisableServerRoutes {
|
if e.config.DisableServerRoutes {
|
||||||
return
|
return
|
||||||
@@ -1832,16 +1884,20 @@ func (e *Engine) updateDNSForwarder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(fwdEntries) > 0 {
|
if len(fwdEntries) > 0 {
|
||||||
if e.dnsForwardMgr == nil {
|
switch {
|
||||||
e.dnsForwardMgr = dnsfwd.NewManager(e.firewall, e.statusRecorder)
|
case e.dnsForwardMgr == nil:
|
||||||
|
e.dnsForwardMgr = dnsfwd.NewManager(e.firewall, e.statusRecorder, forwarderPort)
|
||||||
if err := e.dnsForwardMgr.Start(fwdEntries); err != nil {
|
if err := e.dnsForwardMgr.Start(fwdEntries); err != nil {
|
||||||
log.Errorf("failed to start DNS forward: %v", err)
|
log.Errorf("failed to start DNS forward: %v", err)
|
||||||
e.dnsForwardMgr = nil
|
e.dnsForwardMgr = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("started domain router service with %d entries", len(fwdEntries))
|
log.Infof("started domain router service with %d entries", len(fwdEntries))
|
||||||
} else {
|
case e.dnsFwdPort != forwarderPort:
|
||||||
|
log.Infof("updating domain router service port from %d to %d", e.dnsFwdPort, forwarderPort)
|
||||||
|
e.restartDnsFwd(fwdEntries, forwarderPort)
|
||||||
|
e.dnsFwdPort = forwarderPort
|
||||||
|
|
||||||
|
default:
|
||||||
e.dnsForwardMgr.UpdateDomains(fwdEntries)
|
e.dnsForwardMgr.UpdateDomains(fwdEntries)
|
||||||
}
|
}
|
||||||
} else if e.dnsForwardMgr != nil {
|
} else if e.dnsForwardMgr != nil {
|
||||||
@@ -1851,6 +1907,20 @@ func (e *Engine) updateDNSForwarder(
|
|||||||
}
|
}
|
||||||
e.dnsForwardMgr = nil
|
e.dnsForwardMgr = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) restartDnsFwd(fwdEntries []*dnsfwd.ForwarderEntry, forwarderPort uint16) {
|
||||||
|
log.Infof("updating domain router service port from %d to %d", e.dnsFwdPort, forwarderPort)
|
||||||
|
// stop and start the forwarder to apply the new port
|
||||||
|
if err := e.dnsForwardMgr.Stop(context.Background()); err != nil {
|
||||||
|
log.Errorf("failed to stop DNS forward: %v", err)
|
||||||
|
}
|
||||||
|
e.dnsForwardMgr = dnsfwd.NewManager(e.firewall, e.statusRecorder, forwarderPort)
|
||||||
|
if err := e.dnsForwardMgr.Start(fwdEntries); err != nil {
|
||||||
|
log.Errorf("failed to start DNS forward: %v", err)
|
||||||
|
e.dnsForwardMgr = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) GetNet() (*netstack.Net, error) {
|
func (e *Engine) GetNet() (*netstack.Net, error) {
|
||||||
@@ -2073,3 +2143,44 @@ func createFile(path string) error {
|
|||||||
}
|
}
|
||||||
return file.Close()
|
return file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertToOfferAnswer(msg *sProto.Message) (*peer.OfferAnswer, error) {
|
||||||
|
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rosenpassPubKey []byte
|
||||||
|
rosenpassAddr string
|
||||||
|
)
|
||||||
|
if cfg := msg.GetBody().GetRosenpassConfig(); cfg != nil {
|
||||||
|
rosenpassPubKey = cfg.GetRosenpassPubKey()
|
||||||
|
rosenpassAddr = cfg.GetRosenpassServerAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle optional SessionID
|
||||||
|
var sessionID *peer.ICESessionID
|
||||||
|
if sessionBytes := msg.GetBody().GetSessionId(); sessionBytes != nil {
|
||||||
|
if id, err := peer.ICESessionIDFromBytes(sessionBytes); err != nil {
|
||||||
|
log.Warnf("Invalid session ID in message: %v", err)
|
||||||
|
sessionID = nil // Set to nil if conversion fails
|
||||||
|
} else {
|
||||||
|
sessionID = &id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offerAnswer := peer.OfferAnswer{
|
||||||
|
IceCredentials: peer.IceCredentials{
|
||||||
|
UFrag: remoteCred.UFrag,
|
||||||
|
Pwd: remoteCred.Pwd,
|
||||||
|
},
|
||||||
|
WgListenPort: int(msg.GetBody().GetWgListenPort()),
|
||||||
|
Version: msg.GetBody().GetNetBirdVersion(),
|
||||||
|
RosenpassPubKey: rosenpassPubKey,
|
||||||
|
RosenpassAddr: rosenpassAddr,
|
||||||
|
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
|
||||||
|
SessionID: sessionID,
|
||||||
|
}
|
||||||
|
return &offerAnswer, nil
|
||||||
|
}
|
||||||
|
|||||||
19
client/internal/engine_generic.go
Normal file
19
client/internal/engine_generic.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !js
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createICEConfig creates ICE configuration for non-WASM environments
|
||||||
|
func (e *Engine) createICEConfig() icemaker.Config {
|
||||||
|
return icemaker.Config{
|
||||||
|
StunTurn: &e.stunTurn,
|
||||||
|
InterfaceBlackList: e.config.IFaceBlackList,
|
||||||
|
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||||
|
UDPMux: e.udpMux.SingleSocketUDPMux,
|
||||||
|
UDPMuxSrflx: e.udpMux,
|
||||||
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
|
}
|
||||||
|
}
|
||||||
18
client/internal/engine_js.go
Normal file
18
client/internal/engine_js.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//go:build js
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createICEConfig creates ICE configuration for WASM environment.
|
||||||
|
func (e *Engine) createICEConfig() icemaker.Config {
|
||||||
|
cfg := icemaker.Config{
|
||||||
|
StunTurn: &e.stunTurn,
|
||||||
|
InterfaceBlackList: e.config.IFaceBlackList,
|
||||||
|
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||||
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
@@ -28,6 +28,10 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||||
|
"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/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
@@ -43,8 +47,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/ssh"
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
mgmt "github.com/netbirdio/netbird/shared/management/client"
|
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
|
||||||
"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"
|
||||||
@@ -54,8 +56,10 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
"github.com/netbirdio/netbird/monotime"
|
"github.com/netbirdio/netbird/monotime"
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
|
mgmt "github.com/netbirdio/netbird/shared/management/client"
|
||||||
|
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
signal "github.com/netbirdio/netbird/shared/signal/client"
|
signal "github.com/netbirdio/netbird/shared/signal/client"
|
||||||
"github.com/netbirdio/netbird/shared/signal/proto"
|
"github.com/netbirdio/netbird/shared/signal/proto"
|
||||||
signalServer "github.com/netbirdio/netbird/signal/server"
|
signalServer "github.com/netbirdio/netbird/signal/server"
|
||||||
@@ -217,22 +221,13 @@ func TestEngine_SSH(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String())
|
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String())
|
||||||
engine := NewEngine(
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||||
ctx, cancel,
|
WgIfaceName: "utun101",
|
||||||
&signal.MockClient{},
|
WgAddr: "100.64.0.1/24",
|
||||||
&mgmt.MockClient{},
|
WgPrivateKey: key,
|
||||||
relayMgr,
|
WgPort: 33100,
|
||||||
&EngineConfig{
|
ServerSSHAllowed: true,
|
||||||
WgIfaceName: "utun101",
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
WgAddr: "100.64.0.1/24",
|
|
||||||
WgPrivateKey: key,
|
|
||||||
WgPort: 33100,
|
|
||||||
ServerSSHAllowed: true,
|
|
||||||
},
|
|
||||||
MobileDependency{},
|
|
||||||
peer.NewRecorder("https://mgm"),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
engine.dnsServer = &dns.MockServer{
|
engine.dnsServer = &dns.MockServer{
|
||||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||||
@@ -362,20 +357,12 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String())
|
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String())
|
||||||
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{
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
WgIfaceName: "utun102",
|
|
||||||
WgAddr: "100.64.0.1/24",
|
|
||||||
WgPrivateKey: key,
|
|
||||||
WgPort: 33100,
|
|
||||||
},
|
|
||||||
MobileDependency{},
|
|
||||||
peer.NewRecorder("https://mgm"),
|
|
||||||
nil)
|
|
||||||
|
|
||||||
wgIface := &MockWGIface{
|
wgIface := &MockWGIface{
|
||||||
NameFunc: func() string { return "utun102" },
|
NameFunc: func() string { return "utun102" },
|
||||||
@@ -593,7 +580,7 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
WgAddr: "100.64.0.1/24",
|
WgAddr: "100.64.0.1/24",
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, 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{
|
||||||
@@ -757,7 +744,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
WgAddr: wgAddr,
|
WgAddr: wgAddr,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
engine.ctx = ctx
|
engine.ctx = ctx
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -958,7 +945,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
WgAddr: wgAddr,
|
WgAddr: wgAddr,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||||
engine.ctx = ctx
|
engine.ctx = ctx
|
||||||
|
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
@@ -1482,7 +1469,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String())
|
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String())
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -1513,15 +1500,15 @@ func startSignal(t *testing.T) (*grpc.Server, string, error) {
|
|||||||
func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, string, error) {
|
func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, string, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
config := &types.Config{
|
config := &config.Config{
|
||||||
Stuns: []*types.Host{},
|
Stuns: []*config.Host{},
|
||||||
TURNConfig: &types.TURNConfig{},
|
TURNConfig: &config.TURNConfig{},
|
||||||
Relay: &types.Relay{
|
Relay: &config.Relay{
|
||||||
Addresses: []string{"127.0.0.1:1234"},
|
Addresses: []string{"127.0.0.1:1234"},
|
||||||
CredentialsTTL: util.Duration{Duration: time.Hour},
|
CredentialsTTL: util.Duration{Duration: time.Hour},
|
||||||
Secret: "222222222222222222",
|
Secret: "222222222222222222",
|
||||||
},
|
},
|
||||||
Signal: &types.Host{
|
Signal: &config.Host{
|
||||||
Proto: "http",
|
Proto: "http",
|
||||||
URI: "localhost:10000",
|
URI: "localhost:10000",
|
||||||
},
|
},
|
||||||
@@ -1542,6 +1529,7 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
t.Cleanup(cleanUp)
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||||
|
jobManager := server.NewJobManager(nil, store)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -1564,14 +1552,15 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
AnyTimes()
|
AnyTimes()
|
||||||
|
|
||||||
permissionsManager := permissions.NewManager(store)
|
permissionsManager := permissions.NewManager(store)
|
||||||
|
groupsManager := groups.NewManagerMock()
|
||||||
|
|
||||||
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
|
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, jobManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
|
secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager, groupsManager)
|
||||||
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil, &server.MockIntegratedValidator{})
|
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, jobManager, secretsManager, nil, &manager.EphemeralManager{}, nil, &server.MockIntegratedValidator{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user