Merge branch 'main' into fix/pkg-loss
162
.github/workflows/golang-test-linux.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Test Code Linux
|
name: Linux
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -12,11 +12,21 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-cache:
|
build-cache:
|
||||||
|
name: "Build Cache"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
outputs:
|
||||||
|
management: ${{ steps.filter.outputs.management }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: dorny/paths-filter@v3
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
management:
|
||||||
|
- 'management/**'
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
@ -38,7 +48,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
||||||
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
@ -89,6 +98,7 @@ jobs:
|
|||||||
run: CGO_ENABLED=1 GOARCH=386 go build -o relay-386 .
|
run: CGO_ENABLED=1 GOARCH=386 go build -o relay-386 .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
name: "Client / Unit"
|
||||||
needs: [build-cache]
|
needs: [build-cache]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -134,9 +144,116 @@ jobs:
|
|||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} CI=true go test -tags devcert -exec 'sudo' -timeout 10m -p 1 $(go list ./... | grep -v /management)
|
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} CI=true go test -tags devcert -exec 'sudo' -timeout 10m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay)
|
||||||
|
|
||||||
|
test_relay:
|
||||||
|
name: "Relay / Unit"
|
||||||
|
needs: [build-cache]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch: [ '386','amd64' ]
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.23.x"
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get Go environment
|
||||||
|
run: |
|
||||||
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.cache }}
|
||||||
|
${{ env.modcache }}
|
||||||
|
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gotest-cache-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev
|
||||||
|
|
||||||
|
- name: Install 32-bit libpcap
|
||||||
|
if: matrix.arch == '386'
|
||||||
|
run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: check git status
|
||||||
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
||||||
|
go test \
|
||||||
|
-exec 'sudo' \
|
||||||
|
-timeout 10m ./signal/...
|
||||||
|
|
||||||
|
test_signal:
|
||||||
|
name: "Signal / Unit"
|
||||||
|
needs: [build-cache]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch: [ '386','amd64' ]
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.23.x"
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get Go environment
|
||||||
|
run: |
|
||||||
|
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||||
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.cache }}
|
||||||
|
${{ env.modcache }}
|
||||||
|
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gotest-cache-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev
|
||||||
|
|
||||||
|
- name: Install 32-bit libpcap
|
||||||
|
if: matrix.arch == '386'
|
||||||
|
run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: check git status
|
||||||
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
||||||
|
go test \
|
||||||
|
-exec 'sudo' \
|
||||||
|
-timeout 10m ./signal/...
|
||||||
|
|
||||||
test_management:
|
test_management:
|
||||||
|
name: "Management / Unit"
|
||||||
needs: [ build-cache ]
|
needs: [ build-cache ]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -194,10 +311,17 @@ jobs:
|
|||||||
run: docker pull mlsmaycon/warmed-mysql:8
|
run: docker pull mlsmaycon/warmed-mysql:8
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=devcert -p 1 -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 10m $(go list ./... | grep /management)
|
run: |
|
||||||
|
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
||||||
|
NETBIRD_STORE_ENGINE=${{ matrix.store }} \
|
||||||
|
go test -tags=devcert \
|
||||||
|
-exec "sudo --preserve-env=CI,NETBIRD_STORE_ENGINE" \
|
||||||
|
-timeout 10m ./management/...
|
||||||
|
|
||||||
benchmark:
|
benchmark:
|
||||||
|
name: "Management / Benchmark"
|
||||||
needs: [ build-cache ]
|
needs: [ build-cache ]
|
||||||
|
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -254,10 +378,17 @@ jobs:
|
|||||||
run: docker pull mlsmaycon/warmed-mysql:8
|
run: docker pull mlsmaycon/warmed-mysql:8
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags devcert -run=^$ -bench=. -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 20m ./...
|
run: |
|
||||||
|
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
||||||
|
NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true \
|
||||||
|
go test -tags devcert -run=^$ -bench=. \
|
||||||
|
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \
|
||||||
|
-timeout 20m ./...
|
||||||
|
|
||||||
api_benchmark:
|
api_benchmark:
|
||||||
|
name: "Management / Benchmark (API)"
|
||||||
needs: [ build-cache ]
|
needs: [ build-cache ]
|
||||||
|
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -312,12 +443,21 @@ jobs:
|
|||||||
- name: download mysql image
|
- name: download mysql image
|
||||||
if: matrix.store == 'mysql'
|
if: matrix.store == 'mysql'
|
||||||
run: docker pull mlsmaycon/warmed-mysql:8
|
run: docker pull mlsmaycon/warmed-mysql:8
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -run=^$ -tags=benchmark -bench=. -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 30m $(go list -tags=benchmark ./... | grep /management)
|
run: |
|
||||||
|
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
||||||
|
NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true \
|
||||||
|
go test -tags=benchmark \
|
||||||
|
-run=^$ \
|
||||||
|
-bench=. \
|
||||||
|
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \
|
||||||
|
-timeout 20m ./management/...
|
||||||
|
|
||||||
api_integration_test:
|
api_integration_test:
|
||||||
|
name: "Management / Integration"
|
||||||
needs: [ build-cache ]
|
needs: [ build-cache ]
|
||||||
|
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -363,9 +503,15 @@ jobs:
|
|||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=integration -p 1 -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 30m $(go list -tags=integration ./... | grep /management)
|
run: |
|
||||||
|
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
||||||
|
NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true \
|
||||||
|
go test -tags=integration \
|
||||||
|
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \
|
||||||
|
-timeout 10m ./management/...
|
||||||
|
|
||||||
test_client_on_docker:
|
test_client_on_docker:
|
||||||
|
name: "Client (Docker) / Unit"
|
||||||
needs: [ build-cache ]
|
needs: [ build-cache ]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
|
1
.gitignore
vendored
@ -29,3 +29,4 @@ infrastructure_files/setup.env
|
|||||||
infrastructure_files/setup-*.env
|
infrastructure_files/setup-*.env
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
vendor/
|
||||||
|
@ -103,7 +103,7 @@ linters:
|
|||||||
- predeclared # predeclared finds code that shadows one of Go's predeclared identifiers
|
- predeclared # predeclared finds code that shadows one of Go's predeclared identifiers
|
||||||
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.
|
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.
|
||||||
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
|
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
|
||||||
- thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers.
|
# - thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers.
|
||||||
- wastedassign # wastedassign finds wasted assignment statements
|
- wastedassign # wastedassign finds wasted assignment statements
|
||||||
issues:
|
issues:
|
||||||
# Maximum count of issues with the same text.
|
# Maximum count of issues with the same text.
|
||||||
|
@ -53,7 +53,7 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: client/ui/netbird.desktop
|
- src: client/ui/netbird.desktop
|
||||||
dst: /usr/share/applications/netbird.desktop
|
dst: /usr/share/applications/netbird.desktop
|
||||||
- src: client/ui/netbird-systemtray-connected.png
|
- src: client/ui/netbird.png
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
@ -70,7 +70,7 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: client/ui/netbird.desktop
|
- src: client/ui/netbird.desktop
|
||||||
dst: /usr/share/applications/netbird.desktop
|
dst: /usr/share/applications/netbird.desktop
|
||||||
- src: client/ui/netbird-systemtray-connected.png
|
- src: client/ui/netbird.png
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
<a href="https://netbird.io/webinars/achieve-zero-trust-access-to-k8s?utm_source=github&utm_campaign=2502%20-%20webinar%20-%20How%20to%20Achieve%20Zero%20Trust%20Access%20to%20Kubernetes%20-%20Effortlessly&utm_medium=github">
|
||||||
|
Webinar: How to Achieve Zero Trust Access to Kubernetes — Effortlessly
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="234" src="docs/media/logo-full.png"/>
|
<img width="234" src="docs/media/logo-full.png"/>
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.21.0
|
FROM alpine:3.21.3
|
||||||
RUN apk add --no-cache ca-certificates iptables ip6tables
|
RUN apk add --no-cache ca-certificates iptables ip6tables
|
||||||
ENV NB_FOREGROUND_MODE=true
|
ENV NB_FOREGROUND_MODE=true
|
||||||
ENTRYPOINT [ "/usr/local/bin/netbird","up"]
|
ENTRYPOINT [ "/usr/local/bin/netbird","up"]
|
||||||
|
@ -85,11 +85,17 @@ var loginCmd = &cobra.Command{
|
|||||||
|
|
||||||
client := proto.NewDaemonServiceClient(conn)
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
|
var dnsLabelsReq []string
|
||||||
|
if dnsLabelsValidated != nil {
|
||||||
|
dnsLabelsReq = dnsLabelsValidated.ToSafeStringList()
|
||||||
|
}
|
||||||
|
|
||||||
loginRequest := proto.LoginRequest{
|
loginRequest := proto.LoginRequest{
|
||||||
SetupKey: providedSetupKey,
|
SetupKey: providedSetupKey,
|
||||||
ManagementUrl: managementURL,
|
ManagementUrl: managementURL,
|
||||||
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
Hostname: hostName,
|
Hostname: hostName,
|
||||||
|
DnsLabels: dnsLabelsReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
||||||
|
@ -39,7 +39,6 @@ type peerStateDetailOutput struct {
|
|||||||
TransferSent int64 `json:"transferSent" yaml:"transferSent"`
|
TransferSent int64 `json:"transferSent" yaml:"transferSent"`
|
||||||
Latency time.Duration `json:"latency" yaml:"latency"`
|
Latency time.Duration `json:"latency" yaml:"latency"`
|
||||||
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
||||||
Routes []string `json:"routes" yaml:"routes"`
|
|
||||||
Networks []string `json:"networks" yaml:"networks"`
|
Networks []string `json:"networks" yaml:"networks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,9 +97,9 @@ type statusOutputOverview struct {
|
|||||||
FQDN string `json:"fqdn" yaml:"fqdn"`
|
FQDN string `json:"fqdn" yaml:"fqdn"`
|
||||||
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
||||||
RosenpassPermissive bool `json:"quantumResistancePermissive" yaml:"quantumResistancePermissive"`
|
RosenpassPermissive bool `json:"quantumResistancePermissive" yaml:"quantumResistancePermissive"`
|
||||||
Routes []string `json:"routes" yaml:"routes"`
|
|
||||||
Networks []string `json:"networks" yaml:"networks"`
|
Networks []string `json:"networks" yaml:"networks"`
|
||||||
NSServerGroups []nsServerGroupStateOutput `json:"dnsServers" yaml:"dnsServers"`
|
NSServerGroups []nsServerGroupStateOutput `json:"dnsServers" yaml:"dnsServers"`
|
||||||
|
Events []systemEventOutput `json:"events" yaml:"events"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -284,9 +283,9 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
|
|||||||
FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(),
|
FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(),
|
||||||
RosenpassEnabled: pbFullStatus.GetLocalPeerState().GetRosenpassEnabled(),
|
RosenpassEnabled: pbFullStatus.GetLocalPeerState().GetRosenpassEnabled(),
|
||||||
RosenpassPermissive: pbFullStatus.GetLocalPeerState().GetRosenpassPermissive(),
|
RosenpassPermissive: pbFullStatus.GetLocalPeerState().GetRosenpassPermissive(),
|
||||||
Routes: pbFullStatus.GetLocalPeerState().GetNetworks(),
|
|
||||||
Networks: pbFullStatus.GetLocalPeerState().GetNetworks(),
|
Networks: pbFullStatus.GetLocalPeerState().GetNetworks(),
|
||||||
NSServerGroups: mapNSGroups(pbFullStatus.GetDnsServers()),
|
NSServerGroups: mapNSGroups(pbFullStatus.GetDnsServers()),
|
||||||
|
Events: mapEvents(pbFullStatus.GetEvents()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if anonymizeFlag {
|
if anonymizeFlag {
|
||||||
@ -393,7 +392,6 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput {
|
|||||||
TransferSent: transferSent,
|
TransferSent: transferSent,
|
||||||
Latency: pbPeerState.GetLatency().AsDuration(),
|
Latency: pbPeerState.GetLatency().AsDuration(),
|
||||||
RosenpassEnabled: pbPeerState.GetRosenpassEnabled(),
|
RosenpassEnabled: pbPeerState.GetRosenpassEnabled(),
|
||||||
Routes: pbPeerState.GetNetworks(),
|
|
||||||
Networks: pbPeerState.GetNetworks(),
|
Networks: pbPeerState.GetNetworks(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +557,6 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
"NetBird IP: %s\n"+
|
"NetBird IP: %s\n"+
|
||||||
"Interface type: %s\n"+
|
"Interface type: %s\n"+
|
||||||
"Quantum resistance: %s\n"+
|
"Quantum resistance: %s\n"+
|
||||||
"Routes: %s\n"+
|
|
||||||
"Networks: %s\n"+
|
"Networks: %s\n"+
|
||||||
"Peers count: %s\n",
|
"Peers count: %s\n",
|
||||||
fmt.Sprintf("%s/%s%s", goos, goarch, goarm),
|
fmt.Sprintf("%s/%s%s", goos, goarch, goarm),
|
||||||
@ -574,7 +571,6 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
interfaceTypeString,
|
interfaceTypeString,
|
||||||
rosenpassEnabledStatus,
|
rosenpassEnabledStatus,
|
||||||
networks,
|
networks,
|
||||||
networks,
|
|
||||||
peersCountString,
|
peersCountString,
|
||||||
)
|
)
|
||||||
return summary
|
return summary
|
||||||
@ -582,13 +578,17 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
|
|
||||||
func parseToFullDetailSummary(overview statusOutputOverview) string {
|
func parseToFullDetailSummary(overview statusOutputOverview) string {
|
||||||
parsedPeersString := parsePeers(overview.Peers, overview.RosenpassEnabled, overview.RosenpassPermissive)
|
parsedPeersString := parsePeers(overview.Peers, overview.RosenpassEnabled, overview.RosenpassPermissive)
|
||||||
|
parsedEventsString := parseEvents(overview.Events)
|
||||||
summary := parseGeneralSummary(overview, true, true, true)
|
summary := parseGeneralSummary(overview, true, true, true)
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"Peers detail:"+
|
"Peers detail:"+
|
||||||
|
"%s\n"+
|
||||||
|
"Events:"+
|
||||||
"%s\n"+
|
"%s\n"+
|
||||||
"%s",
|
"%s",
|
||||||
parsedPeersString,
|
parsedPeersString,
|
||||||
|
parsedEventsString,
|
||||||
summary,
|
summary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -657,7 +657,6 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
" Last WireGuard handshake: %s\n"+
|
" Last WireGuard handshake: %s\n"+
|
||||||
" Transfer status (received/sent) %s/%s\n"+
|
" Transfer status (received/sent) %s/%s\n"+
|
||||||
" Quantum resistance: %s\n"+
|
" Quantum resistance: %s\n"+
|
||||||
" Routes: %s\n"+
|
|
||||||
" Networks: %s\n"+
|
" Networks: %s\n"+
|
||||||
" Latency: %s\n",
|
" Latency: %s\n",
|
||||||
peerState.FQDN,
|
peerState.FQDN,
|
||||||
@ -676,7 +675,6 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
toIEC(peerState.TransferSent),
|
toIEC(peerState.TransferSent),
|
||||||
rosenpassEnabledStatus,
|
rosenpassEnabledStatus,
|
||||||
networks,
|
networks,
|
||||||
networks,
|
|
||||||
peerState.Latency.String(),
|
peerState.Latency.String(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -825,14 +823,6 @@ func anonymizePeerDetail(a *anonymize.Anonymizer, peer *peerStateDetailOutput) {
|
|||||||
for i, route := range peer.Networks {
|
for i, route := range peer.Networks {
|
||||||
peer.Networks[i] = a.AnonymizeRoute(route)
|
peer.Networks[i] = a.AnonymizeRoute(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, route := range peer.Routes {
|
|
||||||
peer.Routes[i] = a.AnonymizeIPString(route)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, route := range peer.Routes {
|
|
||||||
peer.Routes[i] = a.AnonymizeRoute(route)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func anonymizeOverview(a *anonymize.Anonymizer, overview *statusOutputOverview) {
|
func anonymizeOverview(a *anonymize.Anonymizer, overview *statusOutputOverview) {
|
||||||
@ -870,9 +860,14 @@ func anonymizeOverview(a *anonymize.Anonymizer, overview *statusOutputOverview)
|
|||||||
overview.Networks[i] = a.AnonymizeRoute(route)
|
overview.Networks[i] = a.AnonymizeRoute(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, route := range overview.Routes {
|
|
||||||
overview.Routes[i] = a.AnonymizeRoute(route)
|
|
||||||
}
|
|
||||||
|
|
||||||
overview.FQDN = a.AnonymizeDomain(overview.FQDN)
|
overview.FQDN = a.AnonymizeDomain(overview.FQDN)
|
||||||
|
|
||||||
|
for i, event := range overview.Events {
|
||||||
|
overview.Events[i].Message = a.AnonymizeString(event.Message)
|
||||||
|
overview.Events[i].UserMessage = a.AnonymizeString(event.UserMessage)
|
||||||
|
|
||||||
|
for k, v := range event.Metadata {
|
||||||
|
event.Metadata[k] = a.AnonymizeString(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
69
client/cmd/status_event.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type systemEventOutput struct {
|
||||||
|
ID string `json:"id" yaml:"id"`
|
||||||
|
Severity string `json:"severity" yaml:"severity"`
|
||||||
|
Category string `json:"category" yaml:"category"`
|
||||||
|
Message string `json:"message" yaml:"message"`
|
||||||
|
UserMessage string `json:"userMessage" yaml:"userMessage"`
|
||||||
|
Timestamp time.Time `json:"timestamp" yaml:"timestamp"`
|
||||||
|
Metadata map[string]string `json:"metadata" yaml:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapEvents(protoEvents []*proto.SystemEvent) []systemEventOutput {
|
||||||
|
events := make([]systemEventOutput, len(protoEvents))
|
||||||
|
for i, event := range protoEvents {
|
||||||
|
events[i] = systemEventOutput{
|
||||||
|
ID: event.GetId(),
|
||||||
|
Severity: event.GetSeverity().String(),
|
||||||
|
Category: event.GetCategory().String(),
|
||||||
|
Message: event.GetMessage(),
|
||||||
|
UserMessage: event.GetUserMessage(),
|
||||||
|
Timestamp: event.GetTimestamp().AsTime(),
|
||||||
|
Metadata: event.GetMetadata(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEvents(events []systemEventOutput) string {
|
||||||
|
if len(events) == 0 {
|
||||||
|
return " No events recorded"
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventsString strings.Builder
|
||||||
|
for _, event := range events {
|
||||||
|
timeStr := timeAgo(event.Timestamp)
|
||||||
|
|
||||||
|
metadataStr := ""
|
||||||
|
if len(event.Metadata) > 0 {
|
||||||
|
pairs := make([]string, 0, len(event.Metadata))
|
||||||
|
for k, v := range event.Metadata {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s: %s", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(pairs)
|
||||||
|
metadataStr = fmt.Sprintf("\n Metadata: %s", strings.Join(pairs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsString.WriteString(fmt.Sprintf("\n [%s] %s (%s)"+
|
||||||
|
"\n Message: %s"+
|
||||||
|
"\n Time: %s%s",
|
||||||
|
event.Severity,
|
||||||
|
event.Category,
|
||||||
|
event.ID,
|
||||||
|
event.Message,
|
||||||
|
timeStr,
|
||||||
|
metadataStr,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return eventsString.String()
|
||||||
|
}
|
@ -146,9 +146,6 @@ var overview = statusOutputOverview{
|
|||||||
LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC),
|
LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC),
|
||||||
TransferReceived: 200,
|
TransferReceived: 200,
|
||||||
TransferSent: 100,
|
TransferSent: 100,
|
||||||
Routes: []string{
|
|
||||||
"10.1.0.0/24",
|
|
||||||
},
|
|
||||||
Networks: []string{
|
Networks: []string{
|
||||||
"10.1.0.0/24",
|
"10.1.0.0/24",
|
||||||
},
|
},
|
||||||
@ -176,6 +173,7 @@ var overview = statusOutputOverview{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Events: []systemEventOutput{},
|
||||||
CliVersion: version.NetbirdVersion(),
|
CliVersion: version.NetbirdVersion(),
|
||||||
DaemonVersion: "0.14.1",
|
DaemonVersion: "0.14.1",
|
||||||
ManagementState: managementStateOutput{
|
ManagementState: managementStateOutput{
|
||||||
@ -230,9 +228,6 @@ var overview = statusOutputOverview{
|
|||||||
Error: "timeout",
|
Error: "timeout",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Routes: []string{
|
|
||||||
"10.10.0.0/24",
|
|
||||||
},
|
|
||||||
Networks: []string{
|
Networks: []string{
|
||||||
"10.10.0.0/24",
|
"10.10.0.0/24",
|
||||||
},
|
},
|
||||||
@ -299,9 +294,6 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"transferSent": 100,
|
"transferSent": 100,
|
||||||
"latency": 10000000,
|
"latency": 10000000,
|
||||||
"quantumResistance": false,
|
"quantumResistance": false,
|
||||||
"routes": [
|
|
||||||
"10.1.0.0/24"
|
|
||||||
],
|
|
||||||
"networks": [
|
"networks": [
|
||||||
"10.1.0.0/24"
|
"10.1.0.0/24"
|
||||||
]
|
]
|
||||||
@ -327,7 +319,6 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"transferSent": 1000,
|
"transferSent": 1000,
|
||||||
"latency": 10000000,
|
"latency": 10000000,
|
||||||
"quantumResistance": false,
|
"quantumResistance": false,
|
||||||
"routes": null,
|
|
||||||
"networks": null
|
"networks": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -366,9 +357,6 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"fqdn": "some-localhost.awesome-domain.com",
|
"fqdn": "some-localhost.awesome-domain.com",
|
||||||
"quantumResistance": false,
|
"quantumResistance": false,
|
||||||
"quantumResistancePermissive": false,
|
"quantumResistancePermissive": false,
|
||||||
"routes": [
|
|
||||||
"10.10.0.0/24"
|
|
||||||
],
|
|
||||||
"networks": [
|
"networks": [
|
||||||
"10.10.0.0/24"
|
"10.10.0.0/24"
|
||||||
],
|
],
|
||||||
@ -393,7 +381,8 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"error": "timeout"
|
"error": "timeout"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"events": []
|
||||||
}`
|
}`
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
@ -429,8 +418,6 @@ func TestParsingToYAML(t *testing.T) {
|
|||||||
transferSent: 100
|
transferSent: 100
|
||||||
latency: 10ms
|
latency: 10ms
|
||||||
quantumResistance: false
|
quantumResistance: false
|
||||||
routes:
|
|
||||||
- 10.1.0.0/24
|
|
||||||
networks:
|
networks:
|
||||||
- 10.1.0.0/24
|
- 10.1.0.0/24
|
||||||
- fqdn: peer-2.awesome-domain.com
|
- fqdn: peer-2.awesome-domain.com
|
||||||
@ -451,7 +438,6 @@ func TestParsingToYAML(t *testing.T) {
|
|||||||
transferSent: 1000
|
transferSent: 1000
|
||||||
latency: 10ms
|
latency: 10ms
|
||||||
quantumResistance: false
|
quantumResistance: false
|
||||||
routes: []
|
|
||||||
networks: []
|
networks: []
|
||||||
cliVersion: development
|
cliVersion: development
|
||||||
daemonVersion: 0.14.1
|
daemonVersion: 0.14.1
|
||||||
@ -479,8 +465,6 @@ usesKernelInterface: true
|
|||||||
fqdn: some-localhost.awesome-domain.com
|
fqdn: some-localhost.awesome-domain.com
|
||||||
quantumResistance: false
|
quantumResistance: false
|
||||||
quantumResistancePermissive: false
|
quantumResistancePermissive: false
|
||||||
routes:
|
|
||||||
- 10.10.0.0/24
|
|
||||||
networks:
|
networks:
|
||||||
- 10.10.0.0/24
|
- 10.10.0.0/24
|
||||||
dnsServers:
|
dnsServers:
|
||||||
@ -497,6 +481,7 @@ dnsServers:
|
|||||||
- example.net
|
- example.net
|
||||||
enabled: false
|
enabled: false
|
||||||
error: timeout
|
error: timeout
|
||||||
|
events: []
|
||||||
`
|
`
|
||||||
|
|
||||||
assert.Equal(t, expectedYAML, yaml)
|
assert.Equal(t, expectedYAML, yaml)
|
||||||
@ -526,7 +511,6 @@ func TestParsingToDetail(t *testing.T) {
|
|||||||
Last WireGuard handshake: %s
|
Last WireGuard handshake: %s
|
||||||
Transfer status (received/sent) 200 B/100 B
|
Transfer status (received/sent) 200 B/100 B
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: 10.1.0.0/24
|
|
||||||
Networks: 10.1.0.0/24
|
Networks: 10.1.0.0/24
|
||||||
Latency: 10ms
|
Latency: 10ms
|
||||||
|
|
||||||
@ -543,10 +527,10 @@ func TestParsingToDetail(t *testing.T) {
|
|||||||
Last WireGuard handshake: %s
|
Last WireGuard handshake: %s
|
||||||
Transfer status (received/sent) 2.0 KiB/1000 B
|
Transfer status (received/sent) 2.0 KiB/1000 B
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: -
|
|
||||||
Networks: -
|
Networks: -
|
||||||
Latency: 10ms
|
Latency: 10ms
|
||||||
|
|
||||||
|
Events: No events recorded
|
||||||
OS: %s/%s
|
OS: %s/%s
|
||||||
Daemon version: 0.14.1
|
Daemon version: 0.14.1
|
||||||
CLI version: %s
|
CLI version: %s
|
||||||
@ -562,7 +546,6 @@ FQDN: some-localhost.awesome-domain.com
|
|||||||
NetBird IP: 192.168.178.100/16
|
NetBird IP: 192.168.178.100/16
|
||||||
Interface type: Kernel
|
Interface type: Kernel
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: 10.10.0.0/24
|
|
||||||
Networks: 10.10.0.0/24
|
Networks: 10.10.0.0/24
|
||||||
Peers count: 2/2 Connected
|
Peers count: 2/2 Connected
|
||||||
`, lastConnectionUpdate1, lastHandshake1, lastConnectionUpdate2, lastHandshake2, runtime.GOOS, runtime.GOARCH, overview.CliVersion)
|
`, lastConnectionUpdate1, lastHandshake1, lastConnectionUpdate2, lastHandshake2, runtime.GOOS, runtime.GOARCH, overview.CliVersion)
|
||||||
@ -584,7 +567,6 @@ FQDN: some-localhost.awesome-domain.com
|
|||||||
NetBird IP: 192.168.178.100/16
|
NetBird IP: 192.168.178.100/16
|
||||||
Interface type: Kernel
|
Interface type: Kernel
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: 10.10.0.0/24
|
|
||||||
Networks: 10.10.0.0/24
|
Networks: 10.10.0.0/24
|
||||||
Peers count: 2/2 Connected
|
Peers count: 2/2 Connected
|
||||||
`
|
`
|
||||||
|
@ -95,7 +95,7 @@ func startManagement(t *testing.T, config *mgmt.Config, testFile string) (*grpc.
|
|||||||
}
|
}
|
||||||
|
|
||||||
secretsManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay)
|
secretsManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay)
|
||||||
mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settings.NewManager(store), peersUpdateManager, secretsManager, nil, nil)
|
mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settings.NewManager(store), peersUpdateManager, secretsManager, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,9 +30,16 @@ const (
|
|||||||
interfaceInputType
|
interfaceInputType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dnsLabelsFlag = "extra-dns-labels"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
foregroundMode bool
|
foregroundMode bool
|
||||||
upCmd = &cobra.Command{
|
dnsLabels []string
|
||||||
|
dnsLabelsValidated domain.List
|
||||||
|
|
||||||
|
upCmd = &cobra.Command{
|
||||||
Use: "up",
|
Use: "up",
|
||||||
Short: "install, login and start Netbird client",
|
Short: "install, login and start Netbird client",
|
||||||
RunE: upFunc,
|
RunE: upFunc,
|
||||||
@ -49,6 +57,14 @@ func init() {
|
|||||||
upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening")
|
upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening")
|
||||||
upCmd.PersistentFlags().DurationVar(&dnsRouteInterval, dnsRouteIntervalFlag, time.Minute, "DNS route update interval")
|
upCmd.PersistentFlags().DurationVar(&dnsRouteInterval, dnsRouteIntervalFlag, time.Minute, "DNS route update interval")
|
||||||
upCmd.PersistentFlags().BoolVar(&blockLANAccess, blockLANAccessFlag, false, "Block access to local networks (LAN) when using this peer as a router or exit node")
|
upCmd.PersistentFlags().BoolVar(&blockLANAccess, blockLANAccessFlag, false, "Block access to local networks (LAN) when using this peer as a router or exit node")
|
||||||
|
|
||||||
|
upCmd.PersistentFlags().StringSliceVar(&dnsLabels, dnsLabelsFlag, nil,
|
||||||
|
`Sets DNS labels`+
|
||||||
|
`You can specify a comma-separated list of up to 32 labels. `+
|
||||||
|
`An empty string "" clears the previous configuration. `+
|
||||||
|
`E.g. --extra-dns-labels vpc1 or --extra-dns-labels vpc1,mgmt1 `+
|
||||||
|
`or --extra-dns-labels ""`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func upFunc(cmd *cobra.Command, args []string) error {
|
func upFunc(cmd *cobra.Command, args []string) error {
|
||||||
@ -67,6 +83,11 @@ func upFunc(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnsLabelsValidated, err = validateDnsLabels(dnsLabels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := internal.CtxInitState(cmd.Context())
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
|
||||||
if hostName != "" {
|
if hostName != "" {
|
||||||
@ -98,6 +119,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
NATExternalIPs: natExternalIPs,
|
NATExternalIPs: natExternalIPs,
|
||||||
CustomDNSAddress: customDNSAddressConverted,
|
CustomDNSAddress: customDNSAddressConverted,
|
||||||
ExtraIFaceBlackList: extraIFaceBlackList,
|
ExtraIFaceBlackList: extraIFaceBlackList,
|
||||||
|
DNSLabels: dnsLabelsValidated,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Flag(enableRosenpassFlag).Changed {
|
if cmd.Flag(enableRosenpassFlag).Changed {
|
||||||
@ -240,6 +262,8 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
Hostname: hostName,
|
Hostname: hostName,
|
||||||
ExtraIFaceBlacklist: extraIFaceBlackList,
|
ExtraIFaceBlacklist: extraIFaceBlackList,
|
||||||
|
DnsLabels: dnsLabels,
|
||||||
|
CleanDNSLabels: dnsLabels != nil && len(dnsLabels) == 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
||||||
@ -430,6 +454,24 @@ func parseCustomDNSAddress(modified bool) ([]byte, error) {
|
|||||||
return parsed, nil
|
return parsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateDnsLabels(labels []string) (domain.List, error) {
|
||||||
|
var (
|
||||||
|
domains domain.List
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
domains, err = domain.ValidateDomains(labels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate dns labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isValidAddrPort(input string) bool {
|
func isValidAddrPort(input string) bool {
|
||||||
if input == "" {
|
if input == "" {
|
||||||
return true
|
return true
|
||||||
|
167
client/embed/doc.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Package embed provides a way to embed the NetBird client directly
|
||||||
|
// into Go programs without requiring a separate NetBird client installation.
|
||||||
|
package embed
|
||||||
|
|
||||||
|
// Basic Usage:
|
||||||
|
//
|
||||||
|
// client, err := embed.New(embed.Options{
|
||||||
|
// DeviceName: "my-service",
|
||||||
|
// SetupKey: os.Getenv("NB_SETUP_KEY"),
|
||||||
|
// ManagementURL: os.Getenv("NB_MANAGEMENT_URL"),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
// if err := client.Start(ctx); err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Complete HTTP Server Example:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "context"
|
||||||
|
// "fmt"
|
||||||
|
// "log"
|
||||||
|
// "net/http"
|
||||||
|
// "os"
|
||||||
|
// "os/signal"
|
||||||
|
// "syscall"
|
||||||
|
// "time"
|
||||||
|
//
|
||||||
|
// netbird "github.com/netbirdio/netbird/client/embed"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// // Create client with setup key and device name
|
||||||
|
// client, err := netbird.New(netbird.Options{
|
||||||
|
// DeviceName: "http-server",
|
||||||
|
// SetupKey: os.Getenv("NB_SETUP_KEY"),
|
||||||
|
// ManagementURL: os.Getenv("NB_MANAGEMENT_URL"),
|
||||||
|
// LogOutput: io.Discard,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Start with timeout
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
// if err := client.Start(ctx); err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Create HTTP server
|
||||||
|
// mux := http.NewServeMux()
|
||||||
|
// mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// fmt.Printf("Request from %s: %s %s\n", r.RemoteAddr, r.Method, r.URL.Path)
|
||||||
|
// fmt.Fprintf(w, "Hello from netbird!")
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// // Listen on netbird network
|
||||||
|
// l, err := client.ListenTCP(":8080")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// server := &http.Server{Handler: mux}
|
||||||
|
// go func() {
|
||||||
|
// if err := server.Serve(l); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
// log.Printf("HTTP server error: %v", err)
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
//
|
||||||
|
// log.Printf("HTTP server listening on netbird network port 8080")
|
||||||
|
//
|
||||||
|
// // Handle shutdown
|
||||||
|
// stop := make(chan os.Signal, 1)
|
||||||
|
// signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
// <-stop
|
||||||
|
//
|
||||||
|
// shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// if err := server.Shutdown(shutdownCtx); err != nil {
|
||||||
|
// log.Printf("HTTP shutdown error: %v", err)
|
||||||
|
// }
|
||||||
|
// if err := client.Stop(shutdownCtx); err != nil {
|
||||||
|
// log.Printf("Netbird shutdown error: %v", err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Complete HTTP Client Example:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "context"
|
||||||
|
// "fmt"
|
||||||
|
// "io"
|
||||||
|
// "log"
|
||||||
|
// "os"
|
||||||
|
// "time"
|
||||||
|
//
|
||||||
|
// netbird "github.com/netbirdio/netbird/client/embed"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// // Create client with setup key and device name
|
||||||
|
// client, err := netbird.New(netbird.Options{
|
||||||
|
// DeviceName: "http-client",
|
||||||
|
// SetupKey: os.Getenv("NB_SETUP_KEY"),
|
||||||
|
// ManagementURL: os.Getenv("NB_MANAGEMENT_URL"),
|
||||||
|
// LogOutput: io.Discard,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Start with timeout
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// if err := client.Start(ctx); err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Create HTTP client that uses netbird network
|
||||||
|
// httpClient := client.NewHTTPClient()
|
||||||
|
// httpClient.Timeout = 10 * time.Second
|
||||||
|
//
|
||||||
|
// // Make request to server in netbird network
|
||||||
|
// target := os.Getenv("NB_TARGET")
|
||||||
|
// resp, err := httpClient.Get(target)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
//
|
||||||
|
// // Read and print response
|
||||||
|
// body, err := io.ReadAll(resp.Body)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Printf("Response from server: %s\n", string(body))
|
||||||
|
//
|
||||||
|
// // Clean shutdown
|
||||||
|
// shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// if err := client.Stop(shutdownCtx); err != nil {
|
||||||
|
// log.Printf("Netbird shutdown error: %v", err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The package provides several methods for network operations:
|
||||||
|
// - Dial: Creates outbound connections
|
||||||
|
// - ListenTCP: Creates TCP listeners
|
||||||
|
// - ListenUDP: Creates UDP listeners
|
||||||
|
//
|
||||||
|
// By default, the embed package uses userspace networking mode, which doesn't
|
||||||
|
// require root/admin privileges. For production deployments, consider setting
|
||||||
|
// appropriate config and state paths for persistence.
|
296
client/embed/embed.go
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
package embed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrClientAlreadyStarted = errors.New("client already started")
|
||||||
|
var ErrClientNotStarted = errors.New("client not started")
|
||||||
|
|
||||||
|
// Client manages a netbird embedded client instance
|
||||||
|
type Client struct {
|
||||||
|
deviceName string
|
||||||
|
config *internal.Config
|
||||||
|
mu sync.Mutex
|
||||||
|
cancel context.CancelFunc
|
||||||
|
setupKey string
|
||||||
|
connect *internal.ConnectClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options configures a new Client
|
||||||
|
type Options struct {
|
||||||
|
// DeviceName is this peer's name in the network
|
||||||
|
DeviceName string
|
||||||
|
// SetupKey is used for authentication
|
||||||
|
SetupKey string
|
||||||
|
// ManagementURL overrides the default management server URL
|
||||||
|
ManagementURL string
|
||||||
|
// PreSharedKey is the pre-shared key for the WireGuard interface
|
||||||
|
PreSharedKey string
|
||||||
|
// LogOutput is the output destination for logs (defaults to os.Stderr if nil)
|
||||||
|
LogOutput io.Writer
|
||||||
|
// LogLevel sets the logging level (defaults to info if empty)
|
||||||
|
LogLevel string
|
||||||
|
// NoUserspace disables the userspace networking mode. Needs admin/root privileges
|
||||||
|
NoUserspace bool
|
||||||
|
// ConfigPath is the path to the netbird config file. If empty, the config will be stored in memory and not persisted.
|
||||||
|
ConfigPath string
|
||||||
|
// StatePath is the path to the netbird state file
|
||||||
|
StatePath string
|
||||||
|
// DisableClientRoutes disables the client routes
|
||||||
|
DisableClientRoutes bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new netbird embedded client
|
||||||
|
func New(opts Options) (*Client, error) {
|
||||||
|
if opts.LogOutput != nil {
|
||||||
|
logrus.SetOutput(opts.LogOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.LogLevel != "" {
|
||||||
|
level, err := logrus.ParseLevel(opts.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse log level: %w", err)
|
||||||
|
}
|
||||||
|
logrus.SetLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.NoUserspace {
|
||||||
|
if err := os.Setenv(netstack.EnvUseNetstackMode, "true"); err != nil {
|
||||||
|
return nil, fmt.Errorf("setenv: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv(netstack.EnvSkipProxy, "true"); err != nil {
|
||||||
|
return nil, fmt.Errorf("setenv: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.StatePath != "" {
|
||||||
|
// TODO: Disable state if path not provided
|
||||||
|
if err := os.Setenv("NB_DNS_STATE_FILE", opts.StatePath); err != nil {
|
||||||
|
return nil, fmt.Errorf("setenv: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := true
|
||||||
|
var config *internal.Config
|
||||||
|
var err error
|
||||||
|
input := internal.ConfigInput{
|
||||||
|
ConfigPath: opts.ConfigPath,
|
||||||
|
ManagementURL: opts.ManagementURL,
|
||||||
|
PreSharedKey: &opts.PreSharedKey,
|
||||||
|
DisableServerRoutes: &t,
|
||||||
|
DisableClientRoutes: &opts.DisableClientRoutes,
|
||||||
|
}
|
||||||
|
if opts.ConfigPath != "" {
|
||||||
|
config, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
} else {
|
||||||
|
config, err = internal.CreateInMemoryConfig(input)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
deviceName: opts.DeviceName,
|
||||||
|
setupKey: opts.SetupKey,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins client operation and blocks until the engine has been started successfully or a startup error occurs.
|
||||||
|
// Pass a context with a deadline to limit the time spent waiting for the engine to start.
|
||||||
|
func (c *Client) Start(startCtx context.Context) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.cancel != nil {
|
||||||
|
return ErrClientAlreadyStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
// nolint:staticcheck
|
||||||
|
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, c.deviceName)
|
||||||
|
if err := internal.Login(ctx, c.config, c.setupKey, ""); err != nil {
|
||||||
|
return fmt.Errorf("login: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder := peer.NewRecorder(c.config.ManagementURL.String())
|
||||||
|
client := internal.NewConnectClient(ctx, c.config, recorder)
|
||||||
|
|
||||||
|
// either startup error (permanent backoff err) or nil err (successful engine up)
|
||||||
|
// TODO: make after-startup backoff err available
|
||||||
|
run := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
if err := client.Run(run); err != nil {
|
||||||
|
run <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-startCtx.Done():
|
||||||
|
if stopErr := client.Stop(); stopErr != nil {
|
||||||
|
return fmt.Errorf("stop error after context done. Stop error: %w. Context done: %w", stopErr, startCtx.Err())
|
||||||
|
}
|
||||||
|
return startCtx.Err()
|
||||||
|
case err := <-run:
|
||||||
|
if err != nil {
|
||||||
|
if stopErr := client.Stop(); stopErr != nil {
|
||||||
|
return fmt.Errorf("stop error after failed to startup. Stop error: %w. Start error: %w", stopErr, err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("startup: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.connect = client
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gracefully stops the client.
|
||||||
|
// Pass a context with a deadline to limit the time spent waiting for the engine to stop.
|
||||||
|
func (c *Client) Stop(ctx context.Context) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.connect == nil {
|
||||||
|
return ErrClientNotStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- c.connect.Stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
c.cancel = nil
|
||||||
|
return ctx.Err()
|
||||||
|
case err := <-done:
|
||||||
|
c.cancel = nil
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stop: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial dials a network address in the netbird network.
|
||||||
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
|
func (c *Client) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
connect := c.connect
|
||||||
|
if connect == nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil, ErrClientNotStarted
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
engine := connect.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, errors.New("engine not started")
|
||||||
|
}
|
||||||
|
|
||||||
|
nsnet, err := engine.GetNet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get net: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsnet.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenTCP listens on the given address in the netbird network
|
||||||
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
|
func (c *Client) ListenTCP(address string) (net.Listener, error) {
|
||||||
|
nsnet, addr, err := c.getNet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("split host port: %w", err)
|
||||||
|
}
|
||||||
|
listenAddr := fmt.Sprintf("%s:%s", addr, port)
|
||||||
|
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve: %w", err)
|
||||||
|
}
|
||||||
|
return nsnet.ListenTCP(tcpAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenUDP listens on the given address in the netbird network
|
||||||
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
|
func (c *Client) ListenUDP(address string) (net.PacketConn, error) {
|
||||||
|
nsnet, addr, err := c.getNet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("split host port: %w", err)
|
||||||
|
}
|
||||||
|
listenAddr := fmt.Sprintf("%s:%s", addr, port)
|
||||||
|
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsnet.ListenUDP(udpAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPClient returns a configured http.Client that uses the netbird network for requests.
|
||||||
|
// Not applicable if the userspace networking mode is disabled.
|
||||||
|
func (c *Client) NewHTTPClient() *http.Client {
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: c.Dial,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getNet() (*wgnetstack.Net, netip.Addr, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
connect := c.connect
|
||||||
|
if connect == nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil, netip.Addr{}, errors.New("client not started")
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
engine := connect.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, netip.Addr{}, errors.New("engine not started")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := engine.Address()
|
||||||
|
if err != nil {
|
||||||
|
return nil, netip.Addr{}, fmt.Errorf("engine address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsnet, err := engine.GetNet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, netip.Addr{}, fmt.Errorf("get net: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsnet, addr, nil
|
||||||
|
}
|
@ -173,8 +173,7 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
|
|||||||
stateful: !disableConntrack,
|
stateful: !disableConntrack,
|
||||||
logger: nblog.NewFromLogrus(log.StandardLogger()),
|
logger: nblog.NewFromLogrus(log.StandardLogger()),
|
||||||
netstack: netstack.IsEnabled(),
|
netstack: netstack.IsEnabled(),
|
||||||
// default true for non-netstack, for netstack only if explicitly enabled
|
localForwarding: enableLocalForwarding,
|
||||||
localForwarding: !netstack.IsEnabled() || enableLocalForwarding,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.localipmanager.UpdateLocalIPs(iface); err != nil {
|
if err := m.localipmanager.UpdateLocalIPs(iface); err != nil {
|
||||||
@ -647,11 +646,6 @@ func (m *Manager) dropFilter(packetData []byte) 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 net.IP, packetData []byte) bool {
|
func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP net.IP, packetData []byte) bool {
|
||||||
if !m.localForwarding {
|
|
||||||
m.logger.Trace("Dropping local packet (local forwarding disabled): src=%s dst=%s", srcIP, dstIP)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.peerACLsBlock(srcIP, packetData, m.incomingRules, d) {
|
if m.peerACLsBlock(srcIP, packetData, m.incomingRules, d) {
|
||||||
m.logger.Trace("Dropping local packet (ACL denied): src=%s dst=%s",
|
m.logger.Trace("Dropping local packet (ACL denied): src=%s dst=%s",
|
||||||
srcIP, dstIP)
|
srcIP, dstIP)
|
||||||
@ -660,22 +654,29 @@ func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP net.IP, packetData
|
|||||||
|
|
||||||
// if running in netstack mode we need to pass this to the forwarder
|
// if running in netstack mode we need to pass this to the forwarder
|
||||||
if m.netstack {
|
if m.netstack {
|
||||||
m.handleNetstackLocalTraffic(packetData)
|
return m.handleNetstackLocalTraffic(packetData)
|
||||||
|
|
||||||
// don't process this packet further
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (m *Manager) handleNetstackLocalTraffic(packetData []byte) {
|
|
||||||
|
func (m *Manager) handleNetstackLocalTraffic(packetData []byte) bool {
|
||||||
|
if !m.localForwarding {
|
||||||
|
// pass to virtual tcp/ip stack to be picked up by listeners
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if m.forwarder == nil {
|
if m.forwarder == nil {
|
||||||
return
|
m.logger.Trace("Dropping local packet (forwarder not initialized)")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.forwarder.InjectIncomingPacket(packetData); err != nil {
|
if err := m.forwarder.InjectIncomingPacket(packetData); err != nil {
|
||||||
m.logger.Error("Failed to inject local packet: %v", err)
|
m.logger.Error("Failed to inject local packet: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't process this packet further
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRoutedTraffic handles routed traffic.
|
// handleRoutedTraffic handles routed traffic.
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
@ -18,4 +20,5 @@ type WGTunDevice interface {
|
|||||||
Close() error
|
Close() error
|
||||||
FilteredDevice() *device.FilteredDevice
|
FilteredDevice() *device.FilteredDevice
|
||||||
Device() *wgdevice.Device
|
Device() *wgdevice.Device
|
||||||
|
GetNet() *netstack.Net
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
@ -130,6 +131,10 @@ func (t *WGTunDevice) FilteredDevice() *FilteredDevice {
|
|||||||
return t.filteredDevice
|
return t.filteredDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *WGTunDevice) GetNet() *netstack.Net {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func routesToString(routes []string) string {
|
func routesToString(routes []string) string {
|
||||||
return strings.Join(routes, ";")
|
return strings.Join(routes, ";")
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
@ -143,3 +144,7 @@ func (t *TunDevice) assignAddr() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TunDevice) GetNet() *netstack.Net {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
@ -131,3 +132,7 @@ func (t *TunDevice) UpdateAddr(addr WGAddress) error {
|
|||||||
func (t *TunDevice) FilteredDevice() *FilteredDevice {
|
func (t *TunDevice) FilteredDevice() *FilteredDevice {
|
||||||
return t.filteredDevice
|
return t.filteredDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TunDevice) GetNet() *netstack.Net {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
@ -165,3 +166,7 @@ func (t *TunKernelDevice) FilteredDevice() *FilteredDevice {
|
|||||||
func (t *TunKernelDevice) assignAddr() error {
|
func (t *TunKernelDevice) assignAddr() error {
|
||||||
return t.link.assignAddr(t.address)
|
return t.link.assignAddr(t.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TunKernelDevice) GetNet() *netstack.Net {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -8,10 +8,12 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/netbirdio/netbird/client/iface/netstack"
|
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TunNetstackDevice struct {
|
type TunNetstackDevice struct {
|
||||||
@ -25,9 +27,11 @@ type TunNetstackDevice struct {
|
|||||||
|
|
||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
nsTun *netstack.NetStackTun
|
nsTun *nbnetstack.NetStackTun
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *bind.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
|
|
||||||
|
net *netstack.Net
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetstackDevice(name string, address WGAddress, wgPort int, key string, mtu int, iceBind *bind.ICEBind, listenAddress string) *TunNetstackDevice {
|
func NewNetstackDevice(name string, address WGAddress, wgPort int, key string, mtu int, iceBind *bind.ICEBind, listenAddress string) *TunNetstackDevice {
|
||||||
@ -43,13 +47,19 @@ func NewNetstackDevice(name string, address WGAddress, wgPort int, key string, m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunNetstackDevice) Create() (WGConfigurer, error) {
|
func (t *TunNetstackDevice) Create() (WGConfigurer, error) {
|
||||||
log.Info("create netstack tun interface")
|
log.Info("create nbnetstack tun interface")
|
||||||
t.nsTun = netstack.NewNetStackTun(t.listenAddress, t.address.IP.String(), t.mtu)
|
|
||||||
tunIface, err := t.nsTun.Create()
|
// TODO: get from service listener runtime IP
|
||||||
|
dnsAddr := nbnet.GetLastIPFromNetwork(t.address.Network, 1)
|
||||||
|
log.Debugf("netstack using address: %s", t.address.IP)
|
||||||
|
t.nsTun = nbnetstack.NewNetStackTun(t.listenAddress, t.address.IP, dnsAddr, t.mtu)
|
||||||
|
log.Debugf("netstack using dns address: %s", dnsAddr)
|
||||||
|
tunIface, net, err := t.nsTun.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating tun device: %s", err)
|
return nil, fmt.Errorf("error creating tun device: %s", err)
|
||||||
}
|
}
|
||||||
t.filteredDevice = newDeviceFilter(tunIface)
|
t.filteredDevice = newDeviceFilter(tunIface)
|
||||||
|
t.net = net
|
||||||
|
|
||||||
t.device = device.NewDevice(
|
t.device = device.NewDevice(
|
||||||
t.filteredDevice,
|
t.filteredDevice,
|
||||||
@ -122,3 +132,7 @@ func (t *TunNetstackDevice) FilteredDevice() *FilteredDevice {
|
|||||||
func (t *TunNetstackDevice) Device() *device.Device {
|
func (t *TunNetstackDevice) Device() *device.Device {
|
||||||
return t.device
|
return t.device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TunNetstackDevice) GetNet() *netstack.Net {
|
||||||
|
return t.net
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
@ -135,3 +136,7 @@ func (t *USPDevice) assignAddr() error {
|
|||||||
|
|
||||||
return link.assignAddr(t.address)
|
return link.assignAddr(t.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *USPDevice) GetNet() *netstack.Net {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
@ -174,3 +175,7 @@ func (t *TunDevice) assignAddr() error {
|
|||||||
log.Debugf("adding address %s to interface: %s", t.address.IP, t.name)
|
log.Debugf("adding address %s to interface: %s", t.address.IP, t.name)
|
||||||
return luid.SetIPAddresses([]netip.Prefix{netip.MustParsePrefix(t.address.String())})
|
return luid.SetIPAddresses([]netip.Prefix{netip.MustParsePrefix(t.address.String())})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TunDevice) GetNet() *netstack.Net {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ package iface
|
|||||||
import (
|
import (
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -16,4 +18,5 @@ type WGTunDevice interface {
|
|||||||
Close() error
|
Close() error
|
||||||
FilteredDevice() *device.FilteredDevice
|
FilteredDevice() *device.FilteredDevice
|
||||||
Device() *wgdevice.Device
|
Device() *wgdevice.Device
|
||||||
|
GetNet() *netstack.Net
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
@ -241,3 +242,11 @@ func (w *WGIface) waitUntilRemoved() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNet returns the netstack.Net for the netstack device
|
||||||
|
func (w *WGIface) GetNet() *netstack.Net {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
return w.tun.GetNet()
|
||||||
|
}
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
package iface
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MockWGIface struct {
|
|
||||||
CreateFunc func() error
|
|
||||||
CreateOnAndroidFunc func(routeRange []string, ip string, domains []string) error
|
|
||||||
IsUserspaceBindFunc func() bool
|
|
||||||
NameFunc func() string
|
|
||||||
AddressFunc func() device.WGAddress
|
|
||||||
ToInterfaceFunc func() *net.Interface
|
|
||||||
UpFunc func() (*bind.UniversalUDPMuxDefault, error)
|
|
||||||
UpdateAddrFunc func(newAddr string) error
|
|
||||||
UpdatePeerFunc func(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
|
||||||
RemovePeerFunc func(peerKey string) error
|
|
||||||
AddAllowedIPFunc func(peerKey string, allowedIP string) error
|
|
||||||
RemoveAllowedIPFunc func(peerKey string, allowedIP string) error
|
|
||||||
CloseFunc func() error
|
|
||||||
SetFilterFunc func(filter device.PacketFilter) error
|
|
||||||
GetFilterFunc func() device.PacketFilter
|
|
||||||
GetDeviceFunc func() *device.FilteredDevice
|
|
||||||
GetWGDeviceFunc func() *wgdevice.Device
|
|
||||||
GetStatsFunc func(peerKey string) (configurer.WGStats, error)
|
|
||||||
GetInterfaceGUIDStringFunc func() (string, error)
|
|
||||||
GetProxyFunc func() wgproxy.Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) GetInterfaceGUIDString() (string, error) {
|
|
||||||
return m.GetInterfaceGUIDStringFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) Create() error {
|
|
||||||
return m.CreateFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) CreateOnAndroid(routeRange []string, ip string, domains []string) error {
|
|
||||||
return m.CreateOnAndroidFunc(routeRange, ip, domains)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) IsUserspaceBind() bool {
|
|
||||||
return m.IsUserspaceBindFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) Name() string {
|
|
||||||
return m.NameFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) Address() device.WGAddress {
|
|
||||||
return m.AddressFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) ToInterface() *net.Interface {
|
|
||||||
return m.ToInterfaceFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
|
||||||
return m.UpFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) UpdateAddr(newAddr string) error {
|
|
||||||
return m.UpdateAddrFunc(newAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
|
||||||
return m.UpdatePeerFunc(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) RemovePeer(peerKey string) error {
|
|
||||||
return m.RemovePeerFunc(peerKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) AddAllowedIP(peerKey string, allowedIP string) error {
|
|
||||||
return m.AddAllowedIPFunc(peerKey, allowedIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) RemoveAllowedIP(peerKey string, allowedIP string) error {
|
|
||||||
return m.RemoveAllowedIPFunc(peerKey, allowedIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) Close() error {
|
|
||||||
return m.CloseFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) SetFilter(filter device.PacketFilter) error {
|
|
||||||
return m.SetFilterFunc(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) GetFilter() device.PacketFilter {
|
|
||||||
return m.GetFilterFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) GetDevice() *device.FilteredDevice {
|
|
||||||
return m.GetDeviceFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) GetWGDevice() *wgdevice.Device {
|
|
||||||
return m.GetWGDeviceFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) GetStats(peerKey string) (configurer.WGStats, error) {
|
|
||||||
return m.GetStatsFunc(peerKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockWGIface) GetProxy() wgproxy.Proxy {
|
|
||||||
return m.GetProxyFunc()
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package iface
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IWGIface interface {
|
|
||||||
Create() error
|
|
||||||
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
|
||||||
IsUserspaceBind() bool
|
|
||||||
Name() string
|
|
||||||
Address() device.WGAddress
|
|
||||||
ToInterface() *net.Interface
|
|
||||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
|
||||||
UpdateAddr(newAddr string) error
|
|
||||||
GetProxy() wgproxy.Proxy
|
|
||||||
UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
|
||||||
RemovePeer(peerKey string) error
|
|
||||||
AddAllowedIP(peerKey string, allowedIP string) error
|
|
||||||
RemoveAllowedIP(peerKey string, allowedIP string) error
|
|
||||||
Close() error
|
|
||||||
SetFilter(filter device.PacketFilter) error
|
|
||||||
GetFilter() device.PacketFilter
|
|
||||||
GetDevice() *device.FilteredDevice
|
|
||||||
GetWGDevice() *wgdevice.Device
|
|
||||||
GetStats(peerKey string) (configurer.WGStats, error)
|
|
||||||
GetInterfaceGUIDString() (string, error)
|
|
||||||
}
|
|
@ -8,9 +8,11 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const EnvUseNetstackMode = "NB_USE_NETSTACK_MODE"
|
||||||
|
|
||||||
// IsEnabled todo: move these function to cmd layer
|
// IsEnabled todo: move these function to cmd layer
|
||||||
func IsEnabled() bool {
|
func IsEnabled() bool {
|
||||||
return os.Getenv("NB_USE_NETSTACK_MODE") == "true"
|
return os.Getenv(EnvUseNetstackMode) == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenAddr() string {
|
func ListenAddr() string {
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
package netstack
|
package netstack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const EnvSkipProxy = "NB_NETSTACK_SKIP_PROXY"
|
||||||
|
|
||||||
type NetStackTun struct { //nolint:revive
|
type NetStackTun struct { //nolint:revive
|
||||||
address string
|
address net.IP
|
||||||
|
dnsAddress net.IP
|
||||||
mtu int
|
mtu int
|
||||||
listenAddress string
|
listenAddress string
|
||||||
|
|
||||||
@ -17,29 +24,48 @@ type NetStackTun struct { //nolint:revive
|
|||||||
tundev tun.Device
|
tundev tun.Device
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetStackTun(listenAddress string, address string, mtu int) *NetStackTun {
|
func NewNetStackTun(listenAddress string, address net.IP, dnsAddress net.IP, mtu int) *NetStackTun {
|
||||||
return &NetStackTun{
|
return &NetStackTun{
|
||||||
address: address,
|
address: address,
|
||||||
|
dnsAddress: dnsAddress,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
listenAddress: listenAddress,
|
listenAddress: listenAddress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *NetStackTun) Create() (tun.Device, error) {
|
func (t *NetStackTun) Create() (tun.Device, *netstack.Net, error) {
|
||||||
|
addr, ok := netip.AddrFromSlice(t.address)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("convert address to netip.Addr: %v", t.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsAddr, ok := netip.AddrFromSlice(t.dnsAddress)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("convert dns address to netip.Addr: %v", t.dnsAddress)
|
||||||
|
}
|
||||||
|
|
||||||
nsTunDev, tunNet, err := netstack.CreateNetTUN(
|
nsTunDev, tunNet, err := netstack.CreateNetTUN(
|
||||||
[]netip.Addr{netip.MustParseAddr(t.address)},
|
[]netip.Addr{addr.Unmap()},
|
||||||
[]netip.Addr{},
|
[]netip.Addr{dnsAddr.Unmap()},
|
||||||
t.mtu)
|
t.mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
t.tundev = nsTunDev
|
t.tundev = nsTunDev
|
||||||
|
|
||||||
|
skipProxy, err := strconv.ParseBool(os.Getenv(EnvSkipProxy))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to parse NB_ETSTACK_SKIP_PROXY: %s", err)
|
||||||
|
}
|
||||||
|
if skipProxy {
|
||||||
|
return nsTunDev, tunNet, nil
|
||||||
|
}
|
||||||
|
|
||||||
dialer := NewNSDialer(tunNet)
|
dialer := NewNSDialer(tunNet)
|
||||||
t.proxy, err = NewSocks5(dialer)
|
t.proxy, err = NewSocks5(dialer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = t.tundev.Close()
|
_ = t.tundev.Close()
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -49,7 +75,7 @@ func (t *NetStackTun) Create() (tun.Device, error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nsTunDev, nil
|
return nsTunDev, tunNet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *NetStackTun) Close() error {
|
func (t *NetStackTun) Close() error {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/routemanager/dynamic"
|
"github.com/netbirdio/netbird/client/internal/routemanager/dynamic"
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
mgm "github.com/netbirdio/netbird/management/client"
|
mgm "github.com/netbirdio/netbird/management/client"
|
||||||
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,6 +70,10 @@ type ConfigInput struct {
|
|||||||
DisableFirewall *bool
|
DisableFirewall *bool
|
||||||
|
|
||||||
BlockLANAccess *bool
|
BlockLANAccess *bool
|
||||||
|
|
||||||
|
DisableNotifications *bool
|
||||||
|
|
||||||
|
DNSLabels domain.List
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config Configuration type
|
// Config Configuration type
|
||||||
@ -93,6 +99,10 @@ type Config struct {
|
|||||||
|
|
||||||
BlockLANAccess bool
|
BlockLANAccess bool
|
||||||
|
|
||||||
|
DisableNotifications bool
|
||||||
|
|
||||||
|
DNSLabels domain.List
|
||||||
|
|
||||||
// SSHKey is a private SSH key in a PEM format
|
// SSHKey is a private SSH key in a PEM format
|
||||||
SSHKey string
|
SSHKey string
|
||||||
|
|
||||||
@ -469,6 +479,16 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
|||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.DisableNotifications != nil && *input.DisableNotifications != config.DisableNotifications {
|
||||||
|
if *input.DisableNotifications {
|
||||||
|
log.Infof("disabling notifications")
|
||||||
|
} else {
|
||||||
|
log.Infof("enabling notifications")
|
||||||
|
}
|
||||||
|
config.DisableNotifications = *input.DisableNotifications
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
if input.ClientCertKeyPath != "" {
|
if input.ClientCertKeyPath != "" {
|
||||||
config.ClientCertKeyPath = input.ClientCertKeyPath
|
config.ClientCertKeyPath = input.ClientCertKeyPath
|
||||||
updated = true
|
updated = true
|
||||||
@ -489,6 +509,14 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.DNSLabels != nil && !slices.Equal(config.DNSLabels, input.DNSLabels) {
|
||||||
|
log.Infof("updating DNS labels [ %s ] (old value: [ %s ])",
|
||||||
|
input.DNSLabels.SafeString(),
|
||||||
|
config.DNSLabels.SafeString())
|
||||||
|
config.DNSLabels = input.DNSLabels
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
return updated, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ func loginToManagement(ctx context.Context, client mgm.Client, pubSSHKey []byte,
|
|||||||
config.DisableDNS,
|
config.DisableDNS,
|
||||||
config.DisableFirewall,
|
config.DisableFirewall,
|
||||||
)
|
)
|
||||||
loginResp, err := client.Login(*serverPublicKey, sysInfo, pubSSHKey)
|
loginResp, err := client.Login(*serverPublicKey, sysInfo, pubSSHKey, config.DNSLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
111
client/internal/dns.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createPTRRecord(aRecord nbdns.SimpleRecord, ipNet *net.IPNet) (nbdns.SimpleRecord, bool) {
|
||||||
|
ip := net.ParseIP(aRecord.RData)
|
||||||
|
if ip == nil || ip.To4() == nil {
|
||||||
|
return nbdns.SimpleRecord{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ipNet.Contains(ip) {
|
||||||
|
return nbdns.SimpleRecord{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ipOctets := strings.Split(ip.String(), ".")
|
||||||
|
slices.Reverse(ipOctets)
|
||||||
|
rdnsName := dns.Fqdn(strings.Join(ipOctets, ".") + ".in-addr.arpa")
|
||||||
|
|
||||||
|
return nbdns.SimpleRecord{
|
||||||
|
Name: rdnsName,
|
||||||
|
Type: int(dns.TypePTR),
|
||||||
|
Class: aRecord.Class,
|
||||||
|
TTL: aRecord.TTL,
|
||||||
|
RData: dns.Fqdn(aRecord.Name),
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateReverseZoneName creates the reverse DNS zone name for a given network
|
||||||
|
func generateReverseZoneName(ipNet *net.IPNet) (string, error) {
|
||||||
|
networkIP := ipNet.IP.Mask(ipNet.Mask)
|
||||||
|
maskOnes, _ := ipNet.Mask.Size()
|
||||||
|
|
||||||
|
// round up to nearest byte
|
||||||
|
octetsToUse := (maskOnes + 7) / 8
|
||||||
|
|
||||||
|
octets := strings.Split(networkIP.String(), ".")
|
||||||
|
if octetsToUse > len(octets) {
|
||||||
|
return "", fmt.Errorf("invalid network mask size for reverse DNS: %d", maskOnes)
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseOctets := make([]string, octetsToUse)
|
||||||
|
for i := 0; i < octetsToUse; i++ {
|
||||||
|
reverseOctets[octetsToUse-1-i] = octets[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return dns.Fqdn(strings.Join(reverseOctets, ".") + ".in-addr.arpa"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zoneExists checks if a zone with the given name already exists in the configuration
|
||||||
|
func zoneExists(config *nbdns.Config, zoneName string) bool {
|
||||||
|
for _, zone := range config.CustomZones {
|
||||||
|
if zone.Domain == zoneName {
|
||||||
|
log.Debugf("reverse DNS zone %s already exists", zoneName)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectPTRRecords gathers all PTR records for the given network from A records
|
||||||
|
func collectPTRRecords(config *nbdns.Config, ipNet *net.IPNet) []nbdns.SimpleRecord {
|
||||||
|
var records []nbdns.SimpleRecord
|
||||||
|
|
||||||
|
for _, zone := range config.CustomZones {
|
||||||
|
for _, record := range zone.Records {
|
||||||
|
if record.Type != int(dns.TypeA) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ptrRecord, ok := createPTRRecord(record, ipNet); ok {
|
||||||
|
records = append(records, ptrRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
// addReverseZone adds a reverse DNS zone to the configuration for the given network
|
||||||
|
func addReverseZone(config *nbdns.Config, ipNet *net.IPNet) {
|
||||||
|
zoneName, err := generateReverseZoneName(ipNet)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if zoneExists(config, zoneName) {
|
||||||
|
log.Debugf("reverse DNS zone %s already exists", zoneName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
records := collectPTRRecords(config, ipNet)
|
||||||
|
|
||||||
|
reverseZone := nbdns.CustomZone{
|
||||||
|
Domain: zoneName,
|
||||||
|
Records: records,
|
||||||
|
}
|
||||||
|
|
||||||
|
config.CustomZones = append(config.CustomZones, reverseZone)
|
||||||
|
log.Debugf("added reverse DNS zone: %s with %d records", zoneName, len(records))
|
||||||
|
}
|
@ -9,6 +9,11 @@ import (
|
|||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipv4ReverseZone = ".in-addr.arpa"
|
||||||
|
ipv6ReverseZone = ".ip6.arpa"
|
||||||
|
)
|
||||||
|
|
||||||
type hostManager interface {
|
type hostManager interface {
|
||||||
applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error
|
applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error
|
||||||
restoreHostDNS() error
|
restoreHostDNS() error
|
||||||
@ -94,9 +99,10 @@ func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) HostD
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, customZone := range dnsConfig.CustomZones {
|
for _, customZone := range dnsConfig.CustomZones {
|
||||||
|
matchOnly := strings.HasSuffix(customZone.Domain, ipv4ReverseZone) || strings.HasSuffix(customZone.Domain, ipv6ReverseZone)
|
||||||
config.Domains = append(config.Domains, DomainConfig{
|
config.Domains = append(config.Domains, DomainConfig{
|
||||||
Domain: strings.TrimSuffix(customZone.Domain, "."),
|
Domain: strings.TrimSuffix(customZone.Domain, "."),
|
||||||
MatchOnly: false,
|
MatchOnly: matchOnly,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,11 +131,30 @@ func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
|
|||||||
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) error {
|
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) 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
|
||||||
policyPath := dnsPolicyConfigMatchPath
|
|
||||||
if r.gpo {
|
if r.gpo {
|
||||||
policyPath = gpoDnsPolicyConfigMatchPath
|
if err := r.configureDNSPolicy(gpoDnsPolicyConfigMatchPath, domains, ip); err != nil {
|
||||||
|
return fmt.Errorf("configure GPO DNS policy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.configureDNSPolicy(dnsPolicyConfigMatchPath, domains, ip); err != nil {
|
||||||
|
return fmt.Errorf("configure local DNS policy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := refreshGroupPolicy(); err != nil {
|
||||||
|
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)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureDNSPolicy handles the actual configuration of a DNS policy at the specified path
|
||||||
|
func (r *registryConfigurator) configureDNSPolicy(policyPath string, domains []string, ip string) 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)
|
||||||
}
|
}
|
||||||
@ -162,13 +181,6 @@ func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) er
|
|||||||
return fmt.Errorf("set %s: %w", dnsPolicyConfigConfigOptionsKey, err)
|
return fmt.Errorf("set %s: %w", dnsPolicyConfigConfigOptionsKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.gpo {
|
|
||||||
if err := refreshGroupPolicy(); err != nil {
|
|
||||||
log.Warnf("failed to refresh group policy: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("added %d match domains. Domain list: %s", len(domains), domains)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ type registrationMap map[string]struct{}
|
|||||||
|
|
||||||
type localResolver struct {
|
type localResolver struct {
|
||||||
registeredMap registrationMap
|
registeredMap registrationMap
|
||||||
records sync.Map
|
records sync.Map // key: string (domain_class_type), value: []dns.RR
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *localResolver) MatchSubdomains() bool {
|
func (d *localResolver) MatchSubdomains() bool {
|
||||||
@ -44,11 +44,12 @@ func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
replyMessage := &dns.Msg{}
|
replyMessage := &dns.Msg{}
|
||||||
replyMessage.SetReply(r)
|
replyMessage.SetReply(r)
|
||||||
replyMessage.RecursionAvailable = true
|
replyMessage.RecursionAvailable = true
|
||||||
replyMessage.Rcode = dns.RcodeSuccess
|
|
||||||
|
|
||||||
response := d.lookupRecord(r)
|
// lookup all records matching the question
|
||||||
if response != nil {
|
records := d.lookupRecords(r)
|
||||||
replyMessage.Answer = append(replyMessage.Answer, response)
|
if len(records) > 0 {
|
||||||
|
replyMessage.Rcode = dns.RcodeSuccess
|
||||||
|
replyMessage.Answer = append(replyMessage.Answer, records...)
|
||||||
} else {
|
} else {
|
||||||
replyMessage.Rcode = dns.RcodeNameError
|
replyMessage.Rcode = dns.RcodeNameError
|
||||||
}
|
}
|
||||||
@ -59,38 +60,65 @@ func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *localResolver) lookupRecord(r *dns.Msg) dns.RR {
|
// lookupRecords fetches *all* DNS records matching the first question in r.
|
||||||
|
func (d *localResolver) lookupRecords(r *dns.Msg) []dns.RR {
|
||||||
|
if len(r.Question) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
question := r.Question[0]
|
question := r.Question[0]
|
||||||
question.Name = strings.ToLower(question.Name)
|
question.Name = strings.ToLower(question.Name)
|
||||||
record, found := d.records.Load(buildRecordKey(question.Name, question.Qclass, question.Qtype))
|
key := buildRecordKey(question.Name, question.Qclass, question.Qtype)
|
||||||
|
|
||||||
|
value, found := d.records.Load(key)
|
||||||
if !found {
|
if !found {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return record.(dns.RR)
|
records, ok := value.([]dns.RR)
|
||||||
}
|
if !ok {
|
||||||
|
log.Errorf("failed to cast records to []dns.RR, records: %v", value)
|
||||||
func (d *localResolver) registerRecord(record nbdns.SimpleRecord) error {
|
return nil
|
||||||
fullRecord, err := dns.NewRR(record.String())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("register record: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullRecord.Header().Rdlength = record.Len()
|
// if there's more than one record, rotate them (round-robin)
|
||||||
|
if len(records) > 1 {
|
||||||
|
first := records[0]
|
||||||
|
records = append(records[1:], first)
|
||||||
|
d.records.Store(key, records)
|
||||||
|
}
|
||||||
|
|
||||||
header := fullRecord.Header()
|
return records
|
||||||
d.records.Store(buildRecordKey(header.Name, header.Class, header.Rrtype), fullRecord)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// registerRecord stores a new record by appending it to any existing list
|
||||||
|
func (d *localResolver) registerRecord(record nbdns.SimpleRecord) (string, error) {
|
||||||
|
rr, err := dns.NewRR(record.String())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("register record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Header().Rdlength = record.Len()
|
||||||
|
header := rr.Header()
|
||||||
|
key := buildRecordKey(header.Name, header.Class, header.Rrtype)
|
||||||
|
|
||||||
|
// load any existing slice of records, then append
|
||||||
|
existing, _ := d.records.LoadOrStore(key, []dns.RR{})
|
||||||
|
records := existing.([]dns.RR)
|
||||||
|
records = append(records, rr)
|
||||||
|
|
||||||
|
// store updated slice
|
||||||
|
d.records.Store(key, records)
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteRecord removes *all* records under the recordKey.
|
||||||
func (d *localResolver) deleteRecord(recordKey string) {
|
func (d *localResolver) deleteRecord(recordKey string) {
|
||||||
d.records.Delete(dns.Fqdn(recordKey))
|
d.records.Delete(dns.Fqdn(recordKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildRecordKey consistently generates a key: name_class_type
|
||||||
func buildRecordKey(name string, class, qType uint16) string {
|
func buildRecordKey(name string, class, qType uint16) string {
|
||||||
key := fmt.Sprintf("%s_%d_%d", name, class, qType)
|
return fmt.Sprintf("%s_%d_%d", dns.Fqdn(name), class, qType)
|
||||||
return key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *localResolver) probeAvailability() {}
|
func (d *localResolver) probeAvailability() {}
|
||||||
|
@ -55,7 +55,7 @@ func TestLocalResolver_ServeDNS(t *testing.T) {
|
|||||||
resolver := &localResolver{
|
resolver := &localResolver{
|
||||||
registeredMap: make(registrationMap),
|
registeredMap: make(registrationMap),
|
||||||
}
|
}
|
||||||
_ = resolver.registerRecord(testCase.inputRecord)
|
_, _ = resolver.registerRecord(testCase.inputRecord)
|
||||||
var responseMSG *dns.Msg
|
var responseMSG *dns.Msg
|
||||||
responseWriter := &mockResponseWriter{
|
responseWriter := &mockResponseWriter{
|
||||||
WriteMsgFunc: func(m *dns.Msg) error {
|
WriteMsgFunc: func(m *dns.Msg) error {
|
||||||
|
@ -393,18 +393,22 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
|
|||||||
s.service.Stop()
|
s.service.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
localMuxUpdates, localRecords, err := s.buildLocalHandlerUpdate(update.CustomZones)
|
localMuxUpdates, localRecordsByDomain, err := s.buildLocalHandlerUpdate(update.CustomZones)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("not applying dns update, error: %v", err)
|
return fmt.Errorf("local handler updater: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
upstreamMuxUpdates, err := s.buildUpstreamHandlerUpdate(update.NameServerGroups)
|
upstreamMuxUpdates, err := s.buildUpstreamHandlerUpdate(update.NameServerGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("not applying dns update, error: %v", err)
|
return fmt.Errorf("upstream handler updater: %w", err)
|
||||||
}
|
}
|
||||||
muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) //nolint:gocritic
|
muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) //nolint:gocritic
|
||||||
|
|
||||||
s.updateMux(muxUpdates)
|
s.updateMux(muxUpdates)
|
||||||
s.updateLocalResolver(localRecords)
|
|
||||||
|
// register local records
|
||||||
|
s.updateLocalResolver(localRecordsByDomain)
|
||||||
|
|
||||||
s.currentConfig = dnsConfigToHostDNSConfig(update, s.service.RuntimeIP(), s.service.RuntimePort())
|
s.currentConfig = dnsConfigToHostDNSConfig(update, s.service.RuntimeIP(), s.service.RuntimePort())
|
||||||
|
|
||||||
hostUpdate := s.currentConfig
|
hostUpdate := s.currentConfig
|
||||||
@ -434,13 +438,17 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]handlerWrapper, map[string]nbdns.SimpleRecord, error) {
|
func (s *DefaultServer) buildLocalHandlerUpdate(
|
||||||
|
customZones []nbdns.CustomZone,
|
||||||
|
) ([]handlerWrapper, map[string][]nbdns.SimpleRecord, error) {
|
||||||
|
|
||||||
var muxUpdates []handlerWrapper
|
var muxUpdates []handlerWrapper
|
||||||
localRecords := make(map[string]nbdns.SimpleRecord, 0)
|
localRecords := make(map[string][]nbdns.SimpleRecord)
|
||||||
|
|
||||||
for _, customZone := range customZones {
|
for _, customZone := range customZones {
|
||||||
if len(customZone.Records) == 0 {
|
if len(customZone.Records) == 0 {
|
||||||
return nil, nil, fmt.Errorf("received an empty list of records")
|
log.Warnf("received a custom zone with empty records, skipping domain: %s", customZone.Domain)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
muxUpdates = append(muxUpdates, handlerWrapper{
|
muxUpdates = append(muxUpdates, handlerWrapper{
|
||||||
@ -449,16 +457,20 @@ func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone)
|
|||||||
priority: PriorityMatchDomain,
|
priority: PriorityMatchDomain,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// group all records under this domain
|
||||||
for _, record := range customZone.Records {
|
for _, record := range customZone.Records {
|
||||||
var class uint16 = dns.ClassINET
|
var class uint16 = dns.ClassINET
|
||||||
if record.Class != nbdns.DefaultClass {
|
if record.Class != nbdns.DefaultClass {
|
||||||
return nil, nil, fmt.Errorf("received an invalid class type: %s", record.Class)
|
log.Warnf("received an invalid class type: %s", record.Class)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
key := buildRecordKey(record.Name, class, uint16(record.Type))
|
key := buildRecordKey(record.Name, class, uint16(record.Type))
|
||||||
localRecords[key] = record
|
|
||||||
|
localRecords[key] = append(localRecords[key], record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return muxUpdates, localRecords, nil
|
return muxUpdates, localRecords, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,7 +606,8 @@ func (s *DefaultServer) updateMux(muxUpdates []handlerWrapper) {
|
|||||||
s.dnsMuxMap = muxUpdateMap
|
s.dnsMuxMap = muxUpdateMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord) {
|
func (s *DefaultServer) updateLocalResolver(update map[string][]nbdns.SimpleRecord) {
|
||||||
|
// remove old records that are no longer present
|
||||||
for key := range s.localResolver.registeredMap {
|
for key := range s.localResolver.registeredMap {
|
||||||
_, found := update[key]
|
_, found := update[key]
|
||||||
if !found {
|
if !found {
|
||||||
@ -603,12 +616,18 @@ func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatedMap := make(registrationMap)
|
updatedMap := make(registrationMap)
|
||||||
for key, record := range update {
|
for _, recs := range update {
|
||||||
err := s.localResolver.registerRecord(record)
|
for _, rec := range recs {
|
||||||
if err != nil {
|
// convert the record to a dns.RR and register
|
||||||
log.Warnf("got an error while registering the record (%s), error: %v", record.String(), err)
|
key, err := s.localResolver.registerRecord(rec)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("got an error while registering the record (%s), error: %v",
|
||||||
|
rec.String(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedMap[key] = struct{}{}
|
||||||
}
|
}
|
||||||
updatedMap[key] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.localResolver.registeredMap = updatedMap
|
s.localResolver.registeredMap = updatedMap
|
||||||
|
@ -266,7 +266,7 @@ func TestUpdateDNSServer(t *testing.T) {
|
|||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Custom Zone Records list Should Fail",
|
name: "Invalid Custom Zone Records list Should Skip",
|
||||||
initLocalMap: make(registrationMap),
|
initLocalMap: make(registrationMap),
|
||||||
initUpstreamMap: make(registeredHandlerMap),
|
initUpstreamMap: make(registeredHandlerMap),
|
||||||
initSerial: 0,
|
initSerial: 0,
|
||||||
@ -285,7 +285,11 @@ func TestUpdateDNSServer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shouldFail: true,
|
expectedUpstreamMap: registeredHandlerMap{generateDummyHandler(".", nameServers).id(): handlerWrapper{
|
||||||
|
domain: ".",
|
||||||
|
handler: dummyHandler,
|
||||||
|
priority: PriorityDefault,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Empty Config Should Succeed and Clean Maps",
|
name: "Empty Config Should Succeed and Clean Maps",
|
||||||
@ -573,7 +577,7 @@ func TestDNSServerStartStop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
defer dnsServer.Stop()
|
defer dnsServer.Stop()
|
||||||
err = dnsServer.localResolver.registerRecord(zoneRecords[0])
|
_, err = dnsServer.localResolver.registerRecord(zoneRecords[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -10,6 +9,8 @@ import (
|
|||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceViaMemory struct {
|
type ServiceViaMemory struct {
|
||||||
@ -27,7 +28,7 @@ func NewServiceViaMemory(wgIface WGIface) *ServiceViaMemory {
|
|||||||
wgInterface: wgIface,
|
wgInterface: wgIface,
|
||||||
dnsMux: dns.NewServeMux(),
|
dnsMux: dns.NewServeMux(),
|
||||||
|
|
||||||
runtimeIP: getLastIPFromNetwork(wgIface.Address().Network, 1),
|
runtimeIP: nbnet.GetLastIPFromNetwork(wgIface.Address().Network, 1).String(),
|
||||||
runtimePort: defaultPort,
|
runtimePort: defaultPort,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
@ -118,22 +119,3 @@ func (s *ServiceViaMemory) filterDNSTraffic() (string, error) {
|
|||||||
|
|
||||||
return filter.AddUDPPacketHook(false, net.ParseIP(s.runtimeIP), uint16(s.runtimePort), hook), nil
|
return filter.AddUDPPacketHook(false, net.ParseIP(s.runtimeIP), uint16(s.runtimePort), hook), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastIPFromNetwork(network *net.IPNet, fromEnd int) string {
|
|
||||||
// Calculate the last IP in the CIDR range
|
|
||||||
var endIP net.IP
|
|
||||||
for i := 0; i < len(network.IP); i++ {
|
|
||||||
endIP = append(endIP, network.IP[i]|^network.Mask[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert to big.Int
|
|
||||||
endInt := big.NewInt(0)
|
|
||||||
endInt.SetBytes(endIP)
|
|
||||||
|
|
||||||
// subtract fromEnd from the last ip
|
|
||||||
fromEndBig := big.NewInt(int64(fromEnd))
|
|
||||||
resultInt := big.NewInt(0)
|
|
||||||
resultInt.Sub(endInt, fromEndBig)
|
|
||||||
|
|
||||||
return net.IP(resultInt.Bytes()).String()
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,8 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetLastIPFromNetwork(t *testing.T) {
|
func TestGetLastIPFromNetwork(t *testing.T) {
|
||||||
@ -23,7 +25,7 @@ func TestGetLastIPFromNetwork(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIP := getLastIPFromNetwork(ipnet, 1)
|
lastIP := nbnet.GetLastIPFromNetwork(ipnet, 1).String()
|
||||||
if lastIP != tt.ip {
|
if lastIP != tt.ip {
|
||||||
t.Errorf("wrong IP address, expected %s: got %s", tt.ip, lastIP)
|
t.Errorf("wrong IP address, expected %s: got %s", tt.ip, lastIP)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -230,6 +231,14 @@ func (u *upstreamResolverBase) probeAvailability() {
|
|||||||
// didn't find a working upstream server, let's disable and try later
|
// didn't find a working upstream server, let's disable and try later
|
||||||
if !success {
|
if !success {
|
||||||
u.disable(errors.ErrorOrNil())
|
u.disable(errors.ErrorOrNil())
|
||||||
|
|
||||||
|
u.statusRecorder.PublishEvent(
|
||||||
|
proto.SystemEvent_WARNING,
|
||||||
|
proto.SystemEvent_DNS,
|
||||||
|
"All upstream servers failed",
|
||||||
|
"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, ", ")},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/pion/ice/v3"
|
"github.com/pion/ice/v3"
|
||||||
"github.com/pion/stun/v2"
|
"github.com/pion/stun/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ import (
|
|||||||
"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/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
"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/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
||||||
@ -153,7 +154,7 @@ type Engine struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
wgInterface iface.IWGIface
|
wgInterface WGIface
|
||||||
|
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *bind.UniversalUDPMuxDefault
|
||||||
|
|
||||||
@ -724,7 +725,7 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error {
|
|||||||
// start SSH server if it wasn't running
|
// start SSH server if it wasn't running
|
||||||
if isNil(e.sshServer) {
|
if isNil(e.sshServer) {
|
||||||
listenAddr := fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort)
|
listenAddr := fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort)
|
||||||
if netstack.IsEnabled() {
|
if nbnetstack.IsEnabled() {
|
||||||
listenAddr = fmt.Sprintf("127.0.0.1:%d", nbssh.DefaultSSHPort)
|
listenAddr = fmt.Sprintf("127.0.0.1:%d", nbssh.DefaultSSHPort)
|
||||||
}
|
}
|
||||||
// nil sshServer means it has not yet been started
|
// nil sshServer means it has not yet been started
|
||||||
@ -952,7 +953,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
protoDNSConfig = &mgmProto.DNSConfig{}
|
protoDNSConfig = &mgmProto.DNSConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.dnsServer.UpdateDNSServer(serial, toDNSConfig(protoDNSConfig)); err != nil {
|
if err := e.dnsServer.UpdateDNSServer(serial, toDNSConfig(protoDNSConfig, e.wgInterface.Address().Network)); err != nil {
|
||||||
log.Errorf("failed to update dns server, err: %v", err)
|
log.Errorf("failed to update dns server, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1021,7 +1022,7 @@ func toRouteDomains(myPubKey string, protoRoutes []*mgmProto.Route) []string {
|
|||||||
return dnsRoutes
|
return dnsRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config {
|
func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig, network *net.IPNet) nbdns.Config {
|
||||||
dnsUpdate := nbdns.Config{
|
dnsUpdate := nbdns.Config{
|
||||||
ServiceEnable: protoDNSConfig.GetServiceEnable(),
|
ServiceEnable: protoDNSConfig.GetServiceEnable(),
|
||||||
CustomZones: make([]nbdns.CustomZone, 0),
|
CustomZones: make([]nbdns.CustomZone, 0),
|
||||||
@ -1061,6 +1062,11 @@ func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config {
|
|||||||
}
|
}
|
||||||
dnsUpdate.NameServerGroups = append(dnsUpdate.NameServerGroups, dnsNSGroup)
|
dnsUpdate.NameServerGroups = append(dnsUpdate.NameServerGroups, dnsNSGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(dnsUpdate.CustomZones) > 0 {
|
||||||
|
addReverseZone(&dnsUpdate, network)
|
||||||
|
}
|
||||||
|
|
||||||
return dnsUpdate
|
return dnsUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1367,7 +1373,7 @@ func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
routes := toRoutes(netMap.GetRoutes())
|
routes := toRoutes(netMap.GetRoutes())
|
||||||
dnsCfg := toDNSConfig(netMap.GetDNSConfig())
|
dnsCfg := toDNSConfig(netMap.GetDNSConfig(), e.wgInterface.Address().Network)
|
||||||
return routes, &dnsCfg, nil
|
return routes, &dnsCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1716,6 +1722,37 @@ func (e *Engine) updateDNSForwarder(enabled bool, domains []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Engine) GetNet() (*netstack.Net, error) {
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
intf := e.wgInterface
|
||||||
|
e.syncMsgMux.Unlock()
|
||||||
|
if intf == nil {
|
||||||
|
return nil, errors.New("wireguard interface not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
nsnet := intf.GetNet()
|
||||||
|
if nsnet == nil {
|
||||||
|
return nil, errors.New("failed to get netstack")
|
||||||
|
}
|
||||||
|
return nsnet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) Address() (netip.Addr, error) {
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
intf := e.wgInterface
|
||||||
|
e.syncMsgMux.Unlock()
|
||||||
|
if intf == nil {
|
||||||
|
return netip.Addr{}, errors.New("wireguard interface not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := e.wgInterface.Address()
|
||||||
|
ip, ok := netip.AddrFromSlice(addr.IP)
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}, errors.New("failed to convert address to netip.Addr")
|
||||||
|
}
|
||||||
|
return ip.Unmap(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// isChecksEqual checks if two slices of checks are equal.
|
// isChecksEqual checks if two slices of checks are equal.
|
||||||
func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
|
func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
|
@ -23,10 +23,11 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"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/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer/guard"
|
"github.com/netbirdio/netbird/client/internal/peer/guard"
|
||||||
@ -48,6 +49,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/signal/proto"
|
"github.com/netbirdio/netbird/signal/proto"
|
||||||
signalServer "github.com/netbirdio/netbird/signal/server"
|
signalServer "github.com/netbirdio/netbird/signal/server"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -64,6 +67,114 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MockWGIface struct {
|
||||||
|
CreateFunc func() error
|
||||||
|
CreateOnAndroidFunc func(routeRange []string, ip string, domains []string) error
|
||||||
|
IsUserspaceBindFunc func() bool
|
||||||
|
NameFunc func() string
|
||||||
|
AddressFunc func() device.WGAddress
|
||||||
|
ToInterfaceFunc func() *net.Interface
|
||||||
|
UpFunc func() (*bind.UniversalUDPMuxDefault, error)
|
||||||
|
UpdateAddrFunc func(newAddr string) error
|
||||||
|
UpdatePeerFunc func(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||||
|
RemovePeerFunc func(peerKey string) error
|
||||||
|
AddAllowedIPFunc func(peerKey string, allowedIP string) error
|
||||||
|
RemoveAllowedIPFunc func(peerKey string, allowedIP string) error
|
||||||
|
CloseFunc func() error
|
||||||
|
SetFilterFunc func(filter device.PacketFilter) error
|
||||||
|
GetFilterFunc func() device.PacketFilter
|
||||||
|
GetDeviceFunc func() *device.FilteredDevice
|
||||||
|
GetWGDeviceFunc func() *wgdevice.Device
|
||||||
|
GetStatsFunc func(peerKey string) (configurer.WGStats, error)
|
||||||
|
GetInterfaceGUIDStringFunc func() (string, error)
|
||||||
|
GetProxyFunc func() wgproxy.Proxy
|
||||||
|
GetNetFunc func() *netstack.Net
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetInterfaceGUIDString() (string, error) {
|
||||||
|
return m.GetInterfaceGUIDStringFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) Create() error {
|
||||||
|
return m.CreateFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) CreateOnAndroid(routeRange []string, ip string, domains []string) error {
|
||||||
|
return m.CreateOnAndroidFunc(routeRange, ip, domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) IsUserspaceBind() bool {
|
||||||
|
return m.IsUserspaceBindFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) Name() string {
|
||||||
|
return m.NameFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) Address() device.WGAddress {
|
||||||
|
return m.AddressFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) ToInterface() *net.Interface {
|
||||||
|
return m.ToInterfaceFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
||||||
|
return m.UpFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) UpdateAddr(newAddr string) error {
|
||||||
|
return m.UpdateAddrFunc(newAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||||
|
return m.UpdatePeerFunc(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) RemovePeer(peerKey string) error {
|
||||||
|
return m.RemovePeerFunc(peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) AddAllowedIP(peerKey string, allowedIP string) error {
|
||||||
|
return m.AddAllowedIPFunc(peerKey, allowedIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) RemoveAllowedIP(peerKey string, allowedIP string) error {
|
||||||
|
return m.RemoveAllowedIPFunc(peerKey, allowedIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) Close() error {
|
||||||
|
return m.CloseFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) SetFilter(filter device.PacketFilter) error {
|
||||||
|
return m.SetFilterFunc(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetFilter() device.PacketFilter {
|
||||||
|
return m.GetFilterFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetDevice() *device.FilteredDevice {
|
||||||
|
return m.GetDeviceFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetWGDevice() *wgdevice.Device {
|
||||||
|
return m.GetWGDeviceFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetStats(peerKey string) (configurer.WGStats, error) {
|
||||||
|
return m.GetStatsFunc(peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetProxy() wgproxy.Proxy {
|
||||||
|
return m.GetProxyFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWGIface) GetNet() *netstack.Net {
|
||||||
|
return m.GetNetFunc()
|
||||||
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
_ = util.InitLog("debug", "console")
|
_ = util.InitLog("debug", "console")
|
||||||
code := m.Run()
|
code := m.Run()
|
||||||
@ -245,11 +356,20 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
peer.NewRecorder("https://mgm"),
|
peer.NewRecorder("https://mgm"),
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
wgIface := &iface.MockWGIface{
|
wgIface := &MockWGIface{
|
||||||
NameFunc: func() string { return "utun102" },
|
NameFunc: func() string { return "utun102" },
|
||||||
RemovePeerFunc: func(peerKey string) error {
|
RemovePeerFunc: func(peerKey string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("10.20.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("10.20.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
engine.wgInterface = wgIface
|
engine.wgInterface = wgIface
|
||||||
engine.routeManager = routemanager.NewManager(routemanager.ManagerConfig{
|
engine.routeManager = routemanager.NewManager(routemanager.ManagerConfig{
|
||||||
@ -692,6 +812,9 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Domain: "0.66.100.in-addr.arpa.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
NameServerGroups: []*mgmtProto.NameServerGroup{
|
NameServerGroups: []*mgmtProto.NameServerGroup{
|
||||||
{
|
{
|
||||||
@ -721,6 +844,9 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Domain: "0.66.100.in-addr.arpa.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expectedNSGroupsLen: 1,
|
expectedNSGroupsLen: 1,
|
||||||
expectedNSGroups: []*nbdns.NameServerGroup{
|
expectedNSGroups: []*nbdns.NameServerGroup{
|
||||||
@ -1226,7 +1352,7 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay)
|
secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay)
|
||||||
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settings.NewManager(store), peersUpdateManager, secretsManager, nil, nil)
|
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settings.NewManager(store), peersUpdateManager, secretsManager, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
8
client/internal/iface.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
type WGIface interface {
|
||||||
|
wgIfaceBase
|
||||||
|
}
|
@ -1,12 +1,11 @@
|
|||||||
//go:build !windows
|
package internal
|
||||||
|
|
||||||
package iface
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
@ -15,7 +14,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IWGIface interface {
|
type wgIfaceBase interface {
|
||||||
Create() error
|
Create() error
|
||||||
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
||||||
IsUserspaceBind() bool
|
IsUserspaceBind() bool
|
||||||
@ -35,4 +34,5 @@ type IWGIface interface {
|
|||||||
GetDevice() *device.FilteredDevice
|
GetDevice() *device.FilteredDevice
|
||||||
GetWGDevice() *wgdevice.Device
|
GetWGDevice() *wgdevice.Device
|
||||||
GetStats(peerKey string) (configurer.WGStats, error)
|
GetStats(peerKey string) (configurer.WGStats, error)
|
||||||
|
GetNet() *netstack.Net
|
||||||
}
|
}
|
6
client/internal/iface_windows.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
type WGIface interface {
|
||||||
|
wgIfaceBase
|
||||||
|
GetInterfaceGUIDString() (string, error)
|
||||||
|
}
|
@ -117,7 +117,7 @@ func doMgmLogin(ctx context.Context, mgmClient *mgm.GrpcClient, pubSSHKey []byte
|
|||||||
config.DisableDNS,
|
config.DisableDNS,
|
||||||
config.DisableFirewall,
|
config.DisableFirewall,
|
||||||
)
|
)
|
||||||
_, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey)
|
_, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey, config.DNSLabels)
|
||||||
return serverKey, err
|
return serverKey, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,17 +52,10 @@ const (
|
|||||||
connPriorityICEP2P ConnPriority = 3
|
connPriorityICEP2P ConnPriority = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
type WgInterface interface {
|
|
||||||
UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
|
||||||
RemovePeer(publicKey string) error
|
|
||||||
GetProxy() wgproxy.Proxy
|
|
||||||
GetStats(peerKey string) (configurer.WGStats, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WgConfig struct {
|
type WgConfig struct {
|
||||||
WgListenPort int
|
WgListenPort int
|
||||||
RemoteKey string
|
RemoteKey string
|
||||||
WgInterface WgInterface
|
WgInterface WGIface
|
||||||
AllowedIps string
|
AllowedIps string
|
||||||
PreSharedKey *wgtypes.Key
|
PreSharedKey *wgtypes.Key
|
||||||
}
|
}
|
||||||
|
17
client/internal/peer/iface.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package peer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WGIface interface {
|
||||||
|
UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||||
|
RemovePeer(peerKey string) error
|
||||||
|
GetStats(peerKey string) (configurer.WGStats, error)
|
||||||
|
GetProxy() wgproxy.Proxy
|
||||||
|
}
|
@ -7,21 +7,31 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
"github.com/netbirdio/netbird/client/internal/relay"
|
"github.com/netbirdio/netbird/client/internal/relay"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/management/domain"
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
relayClient "github.com/netbirdio/netbird/relay/client"
|
relayClient "github.com/netbirdio/netbird/relay/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const eventQueueSize = 10
|
||||||
|
|
||||||
type ResolvedDomainInfo struct {
|
type ResolvedDomainInfo struct {
|
||||||
Prefixes []netip.Prefix
|
Prefixes []netip.Prefix
|
||||||
ParentDomain domain.Domain
|
ParentDomain domain.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventListener interface {
|
||||||
|
OnEvent(event *proto.SystemEvent)
|
||||||
|
}
|
||||||
|
|
||||||
// State contains the latest state of a peer
|
// State contains the latest state of a peer
|
||||||
type State struct {
|
type State struct {
|
||||||
Mux *sync.RWMutex
|
Mux *sync.RWMutex
|
||||||
@ -157,6 +167,10 @@ type Status struct {
|
|||||||
peerListChangedForNotification bool
|
peerListChangedForNotification bool
|
||||||
|
|
||||||
relayMgr *relayClient.Manager
|
relayMgr *relayClient.Manager
|
||||||
|
|
||||||
|
eventMux sync.RWMutex
|
||||||
|
eventStreams map[string]chan *proto.SystemEvent
|
||||||
|
eventQueue *EventQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRecorder returns a new Status instance
|
// NewRecorder returns a new Status instance
|
||||||
@ -164,6 +178,8 @@ func NewRecorder(mgmAddress string) *Status {
|
|||||||
return &Status{
|
return &Status{
|
||||||
peers: make(map[string]State),
|
peers: make(map[string]State),
|
||||||
changeNotify: make(map[string]chan struct{}),
|
changeNotify: make(map[string]chan struct{}),
|
||||||
|
eventStreams: make(map[string]chan *proto.SystemEvent),
|
||||||
|
eventQueue: NewEventQueue(eventQueueSize),
|
||||||
offlinePeers: make([]State, 0),
|
offlinePeers: make([]State, 0),
|
||||||
notifier: newNotifier(),
|
notifier: newNotifier(),
|
||||||
mgmAddress: mgmAddress,
|
mgmAddress: mgmAddress,
|
||||||
@ -806,3 +822,112 @@ func (d *Status) notifyAddressChanged() {
|
|||||||
func (d *Status) numOfPeers() int {
|
func (d *Status) numOfPeers() int {
|
||||||
return len(d.peers) + len(d.offlinePeers)
|
return len(d.peers) + len(d.offlinePeers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PublishEvent adds an event to the queue and distributes it to all subscribers
|
||||||
|
func (d *Status) PublishEvent(
|
||||||
|
severity proto.SystemEvent_Severity,
|
||||||
|
category proto.SystemEvent_Category,
|
||||||
|
msg string,
|
||||||
|
userMsg string,
|
||||||
|
metadata map[string]string,
|
||||||
|
) {
|
||||||
|
event := &proto.SystemEvent{
|
||||||
|
Id: uuid.New().String(),
|
||||||
|
Severity: severity,
|
||||||
|
Category: category,
|
||||||
|
Message: msg,
|
||||||
|
UserMessage: userMsg,
|
||||||
|
Metadata: metadata,
|
||||||
|
Timestamp: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
d.eventMux.Lock()
|
||||||
|
defer d.eventMux.Unlock()
|
||||||
|
|
||||||
|
d.eventQueue.Add(event)
|
||||||
|
|
||||||
|
for _, stream := range d.eventStreams {
|
||||||
|
select {
|
||||||
|
case stream <- event:
|
||||||
|
default:
|
||||||
|
log.Debugf("event stream buffer full, skipping event: %v", event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("event published: %v", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeToEvents returns a new event subscription
|
||||||
|
func (d *Status) SubscribeToEvents() *EventSubscription {
|
||||||
|
d.eventMux.Lock()
|
||||||
|
defer d.eventMux.Unlock()
|
||||||
|
|
||||||
|
id := uuid.New().String()
|
||||||
|
stream := make(chan *proto.SystemEvent, 10)
|
||||||
|
d.eventStreams[id] = stream
|
||||||
|
|
||||||
|
return &EventSubscription{
|
||||||
|
id: id,
|
||||||
|
events: stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromEvents removes an event subscription
|
||||||
|
func (d *Status) UnsubscribeFromEvents(sub *EventSubscription) {
|
||||||
|
if sub == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.eventMux.Lock()
|
||||||
|
defer d.eventMux.Unlock()
|
||||||
|
|
||||||
|
if stream, exists := d.eventStreams[sub.id]; exists {
|
||||||
|
close(stream)
|
||||||
|
delete(d.eventStreams, sub.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEventHistory returns all events in the queue
|
||||||
|
func (d *Status) GetEventHistory() []*proto.SystemEvent {
|
||||||
|
return d.eventQueue.GetAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventQueue struct {
|
||||||
|
maxSize int
|
||||||
|
events []*proto.SystemEvent
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventQueue(size int) *EventQueue {
|
||||||
|
return &EventQueue{
|
||||||
|
maxSize: size,
|
||||||
|
events: make([]*proto.SystemEvent, 0, size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *EventQueue) Add(event *proto.SystemEvent) {
|
||||||
|
q.mutex.Lock()
|
||||||
|
defer q.mutex.Unlock()
|
||||||
|
|
||||||
|
q.events = append(q.events, event)
|
||||||
|
|
||||||
|
if len(q.events) > q.maxSize {
|
||||||
|
q.events = q.events[len(q.events)-q.maxSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *EventQueue) GetAll() []*proto.SystemEvent {
|
||||||
|
q.mutex.RLock()
|
||||||
|
defer q.mutex.RUnlock()
|
||||||
|
|
||||||
|
return slices.Clone(q.events)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventSubscription struct {
|
||||||
|
id string
|
||||||
|
events chan *proto.SystemEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EventSubscription) Events() <-chan *proto.SystemEvent {
|
||||||
|
return s.events
|
||||||
|
}
|
||||||
|
@ -4,21 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
runtime "runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
nbdns "github.com/netbirdio/netbird/client/internal/dns"
|
nbdns "github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/peerstore"
|
"github.com/netbirdio/netbird/client/internal/peerstore"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/dnsinterceptor"
|
"github.com/netbirdio/netbird/client/internal/routemanager/dnsinterceptor"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/dynamic"
|
"github.com/netbirdio/netbird/client/internal/routemanager/dynamic"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/static"
|
"github.com/netbirdio/netbird/client/internal/routemanager/static"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +29,15 @@ const (
|
|||||||
handlerTypeStatic
|
handlerTypeStatic
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type reason int
|
||||||
|
|
||||||
|
const (
|
||||||
|
reasonUnknown reason = iota
|
||||||
|
reasonRouteUpdate
|
||||||
|
reasonPeerUpdate
|
||||||
|
reasonShutdown
|
||||||
|
)
|
||||||
|
|
||||||
type routerPeerStatus struct {
|
type routerPeerStatus struct {
|
||||||
connected bool
|
connected bool
|
||||||
relayed bool
|
relayed bool
|
||||||
@ -52,7 +62,7 @@ type clientNetwork struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
wgInterface iface.IWGIface
|
wgInterface iface.WGIface
|
||||||
routes map[route.ID]*route.Route
|
routes map[route.ID]*route.Route
|
||||||
routeUpdate chan routesUpdate
|
routeUpdate chan routesUpdate
|
||||||
peerStateUpdate chan struct{}
|
peerStateUpdate chan struct{}
|
||||||
@ -65,7 +75,7 @@ type clientNetwork struct {
|
|||||||
func newClientNetworkWatcher(
|
func newClientNetworkWatcher(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dnsRouteInterval time.Duration,
|
dnsRouteInterval time.Duration,
|
||||||
wgInterface iface.IWGIface,
|
wgInterface iface.WGIface,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
rt *route.Route,
|
rt *route.Route,
|
||||||
routeRefCounter *refcounter.RouteRefCounter,
|
routeRefCounter *refcounter.RouteRefCounter,
|
||||||
@ -255,7 +265,7 @@ func (c *clientNetwork) removeRouteFromWireGuardPeer() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
func (c *clientNetwork) removeRouteFromPeerAndSystem(rsn reason) error {
|
||||||
if c.currentChosen == nil {
|
if c.currentChosen == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -269,17 +279,19 @@ func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
|||||||
merr = multierror.Append(merr, fmt.Errorf("remove route: %w", err))
|
merr = multierror.Append(merr, fmt.Errorf("remove route: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.disconnectEvent(rsn)
|
||||||
|
|
||||||
return nberrors.FormatErrorOrNil(merr)
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem(rsn reason) error {
|
||||||
routerPeerStatuses := c.getRouterPeerStatuses()
|
routerPeerStatuses := c.getRouterPeerStatuses()
|
||||||
|
|
||||||
newChosenID := c.getBestRouteFromStatuses(routerPeerStatuses)
|
newChosenID := c.getBestRouteFromStatuses(routerPeerStatuses)
|
||||||
|
|
||||||
// If no route is chosen, remove the route from the peer and system
|
// If no route is chosen, remove the route from the peer and system
|
||||||
if newChosenID == "" {
|
if newChosenID == "" {
|
||||||
if err := c.removeRouteFromPeerAndSystem(); err != nil {
|
if err := c.removeRouteFromPeerAndSystem(rsn); err != nil {
|
||||||
return fmt.Errorf("remove route for peer %s: %w", c.currentChosen.Peer, err)
|
return fmt.Errorf("remove route for peer %s: %w", c.currentChosen.Peer, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,6 +331,58 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) disconnectEvent(rsn reason) {
|
||||||
|
var defaultRoute bool
|
||||||
|
for _, r := range c.routes {
|
||||||
|
if r.Network.Bits() == 0 {
|
||||||
|
defaultRoute = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !defaultRoute {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var severity proto.SystemEvent_Severity
|
||||||
|
var message string
|
||||||
|
var userMessage string
|
||||||
|
meta := make(map[string]string)
|
||||||
|
|
||||||
|
switch rsn {
|
||||||
|
case reasonShutdown:
|
||||||
|
severity = proto.SystemEvent_INFO
|
||||||
|
message = "Default route removed"
|
||||||
|
userMessage = "Exit node disconnected."
|
||||||
|
meta["network"] = c.handler.String()
|
||||||
|
case reasonRouteUpdate:
|
||||||
|
severity = proto.SystemEvent_INFO
|
||||||
|
message = "Default route updated due to configuration change"
|
||||||
|
meta["network"] = c.handler.String()
|
||||||
|
case reasonPeerUpdate:
|
||||||
|
severity = proto.SystemEvent_WARNING
|
||||||
|
message = "Default route disconnected due to peer unreachability"
|
||||||
|
userMessage = "Exit node connection lost. Your internet access might be affected."
|
||||||
|
if c.currentChosen != nil {
|
||||||
|
meta["peer"] = c.currentChosen.Peer
|
||||||
|
meta["network"] = c.handler.String()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
severity = proto.SystemEvent_ERROR
|
||||||
|
message = "Default route disconnected for unknown reason"
|
||||||
|
userMessage = "Exit node disconnected for unknown reasons."
|
||||||
|
meta["network"] = c.handler.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.statusRecorder.PublishEvent(
|
||||||
|
severity,
|
||||||
|
proto.SystemEvent_NETWORK,
|
||||||
|
message,
|
||||||
|
userMessage,
|
||||||
|
meta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *clientNetwork) sendUpdateToClientNetworkWatcher(update routesUpdate) {
|
func (c *clientNetwork) sendUpdateToClientNetworkWatcher(update routesUpdate) {
|
||||||
go func() {
|
go func() {
|
||||||
c.routeUpdate <- update
|
c.routeUpdate <- update
|
||||||
@ -361,12 +425,12 @@ func (c *clientNetwork) peersStateAndUpdateWatcher() {
|
|||||||
select {
|
select {
|
||||||
case <-c.ctx.Done():
|
case <-c.ctx.Done():
|
||||||
log.Debugf("Stopping watcher for network [%v]", c.handler)
|
log.Debugf("Stopping watcher for network [%v]", c.handler)
|
||||||
if err := c.removeRouteFromPeerAndSystem(); err != nil {
|
if err := c.removeRouteFromPeerAndSystem(reasonShutdown); err != nil {
|
||||||
log.Errorf("Failed to remove routes for [%v]: %v", c.handler, err)
|
log.Errorf("Failed to remove routes for [%v]: %v", c.handler, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-c.peerStateUpdate:
|
case <-c.peerStateUpdate:
|
||||||
err := c.recalculateRouteAndUpdatePeerAndSystem()
|
err := c.recalculateRouteAndUpdatePeerAndSystem(reasonPeerUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to recalculate routes for network [%v]: %v", c.handler, err)
|
log.Errorf("Failed to recalculate routes for network [%v]: %v", c.handler, err)
|
||||||
}
|
}
|
||||||
@ -385,7 +449,7 @@ func (c *clientNetwork) peersStateAndUpdateWatcher() {
|
|||||||
|
|
||||||
if isTrueRouteUpdate {
|
if isTrueRouteUpdate {
|
||||||
log.Debug("Client network update contains different routes, recalculating routes")
|
log.Debug("Client network update contains different routes, recalculating routes")
|
||||||
err := c.recalculateRouteAndUpdatePeerAndSystem()
|
err := c.recalculateRouteAndUpdatePeerAndSystem(reasonRouteUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to recalculate routes for network [%v]: %v", c.handler, err)
|
log.Errorf("Failed to recalculate routes for network [%v]: %v", c.handler, err)
|
||||||
}
|
}
|
||||||
@ -404,7 +468,7 @@ func handlerFromRoute(
|
|||||||
allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
|
allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
|
||||||
dnsRouterInteval time.Duration,
|
dnsRouterInteval time.Duration,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
wgInterface iface.IWGIface,
|
wgInterface iface.WGIface,
|
||||||
dnsServer nbdns.Server,
|
dnsServer nbdns.Server,
|
||||||
peerStore *peerstore.Store,
|
peerStore *peerstore.Store,
|
||||||
useNewDNSRoute bool,
|
useNewDNSRoute bool,
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/util"
|
"github.com/netbirdio/netbird/client/internal/routemanager/util"
|
||||||
"github.com/netbirdio/netbird/management/domain"
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
@ -48,7 +48,7 @@ type Route struct {
|
|||||||
currentPeerKey string
|
currentPeerKey string
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
wgInterface iface.IWGIface
|
wgInterface iface.WGIface
|
||||||
resolverAddr string
|
resolverAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ func NewRoute(
|
|||||||
allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
|
allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
|
||||||
interval time.Duration,
|
interval time.Duration,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
wgInterface iface.IWGIface,
|
wgInterface iface.WGIface,
|
||||||
resolverAddr string,
|
resolverAddr string,
|
||||||
) *Route {
|
) *Route {
|
||||||
return &Route{
|
return &Route{
|
||||||
|
9
client/internal/routemanager/iface/iface.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package iface
|
||||||
|
|
||||||
|
// WGIface defines subset methods of interface required for router
|
||||||
|
type WGIface interface {
|
||||||
|
wgIfaceBase
|
||||||
|
}
|
22
client/internal/routemanager/iface/iface_common.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
type wgIfaceBase interface {
|
||||||
|
AddAllowedIP(peerKey string, allowedIP string) error
|
||||||
|
RemoveAllowedIP(peerKey string, allowedIP string) error
|
||||||
|
|
||||||
|
Name() string
|
||||||
|
Address() iface.WGAddress
|
||||||
|
ToInterface() *net.Interface
|
||||||
|
IsUserspaceBind() bool
|
||||||
|
GetFilter() device.PacketFilter
|
||||||
|
GetDevice() *device.FilteredDevice
|
||||||
|
GetStats(peerKey string) (configurer.WGStats, error)
|
||||||
|
}
|
7
client/internal/routemanager/iface/iface_windows.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
// WGIface defines subset methods of interface required for router
|
||||||
|
type WGIface interface {
|
||||||
|
wgIfaceBase
|
||||||
|
GetInterfaceGUIDString() (string, error)
|
||||||
|
}
|
@ -15,13 +15,13 @@ import (
|
|||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
"github.com/netbirdio/netbird/client/iface/netstack"
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/peerstore"
|
"github.com/netbirdio/netbird/client/internal/peerstore"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
|
"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
||||||
@ -52,7 +52,7 @@ type ManagerConfig struct {
|
|||||||
Context context.Context
|
Context context.Context
|
||||||
PublicKey string
|
PublicKey string
|
||||||
DNSRouteInterval time.Duration
|
DNSRouteInterval time.Duration
|
||||||
WGInterface iface.IWGIface
|
WGInterface iface.WGIface
|
||||||
StatusRecorder *peer.Status
|
StatusRecorder *peer.Status
|
||||||
RelayManager *relayClient.Manager
|
RelayManager *relayClient.Manager
|
||||||
InitialRoutes []*route.Route
|
InitialRoutes []*route.Route
|
||||||
@ -74,7 +74,7 @@ type DefaultManager struct {
|
|||||||
sysOps *systemops.SysOps
|
sysOps *systemops.SysOps
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
relayMgr *relayClient.Manager
|
relayMgr *relayClient.Manager
|
||||||
wgInterface iface.IWGIface
|
wgInterface iface.WGIface
|
||||||
pubKey string
|
pubKey string
|
||||||
notifier *notifier.Notifier
|
notifier *notifier.Notifier
|
||||||
routeRefCounter *refcounter.RouteRefCounter
|
routeRefCounter *refcounter.RouteRefCounter
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +22,6 @@ func (r serverRouter) updateRoutes(map[route.ID]*route.Route) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServerRouter(context.Context, iface.IWGIface, firewall.Manager, *peer.Status) (*serverRouter, error) {
|
func newServerRouter(context.Context, iface.WGIface, firewall.Manager, *peer.Status) (*serverRouter, error) {
|
||||||
return nil, fmt.Errorf("server route not supported on this os")
|
return nil, fmt.Errorf("server route not supported on this os")
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
@ -22,11 +22,11 @@ type serverRouter struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
routes map[route.ID]*route.Route
|
routes map[route.ID]*route.Route
|
||||||
firewall firewall.Manager
|
firewall firewall.Manager
|
||||||
wgInterface iface.IWGIface
|
wgInterface iface.WGIface
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServerRouter(ctx context.Context, wgInterface iface.IWGIface, firewall firewall.Manager, statusRecorder *peer.Status) (*serverRouter, error) {
|
func newServerRouter(ctx context.Context, wgInterface iface.WGIface, firewall firewall.Manager, statusRecorder *peer.Status) (*serverRouter, error) {
|
||||||
return &serverRouter{
|
return &serverRouter{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
routes: make(map[route.ID]*route.Route),
|
routes: make(map[route.ID]*route.Route),
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -23,7 +23,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Setup configures sysctl settings for RP filtering and source validation.
|
// Setup configures sysctl settings for RP filtering and source validation.
|
||||||
func Setup(wgIface iface.IWGIface) (map[string]int, error) {
|
func Setup(wgIface iface.WGIface) (map[string]int, error) {
|
||||||
keys := map[string]int{}
|
keys := map[string]int{}
|
||||||
var result *multierror.Error
|
var result *multierror.Error
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
|
"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
||||||
)
|
)
|
||||||
@ -19,7 +19,7 @@ type ExclusionCounter = refcounter.Counter[netip.Prefix, struct{}, Nexthop]
|
|||||||
|
|
||||||
type SysOps struct {
|
type SysOps struct {
|
||||||
refCounter *ExclusionCounter
|
refCounter *ExclusionCounter
|
||||||
wgInterface iface.IWGIface
|
wgInterface iface.WGIface
|
||||||
// prefixes is tracking all the current added prefixes im memory
|
// prefixes is tracking all the current added prefixes im memory
|
||||||
// (this is used in iOS as all route updates require a full table update)
|
// (this is used in iOS as all route updates require a full table update)
|
||||||
//nolint
|
//nolint
|
||||||
@ -30,7 +30,7 @@ type SysOps struct {
|
|||||||
notifier *notifier.Notifier
|
notifier *notifier.Notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysOps(wgInterface iface.IWGIface, notifier *notifier.Notifier) *SysOps {
|
func NewSysOps(wgInterface iface.WGIface, notifier *notifier.Notifier) *SysOps {
|
||||||
return &SysOps{
|
return &SysOps{
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
|
@ -16,8 +16,8 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/netstack"
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/util"
|
"github.com/netbirdio/netbird/client/internal/routemanager/util"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager/vars"
|
"github.com/netbirdio/netbird/client/internal/routemanager/vars"
|
||||||
@ -149,7 +149,7 @@ func (r *SysOps) addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
|||||||
|
|
||||||
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
||||||
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
||||||
func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf iface.IWGIface, initialNextHop Nexthop) (Nexthop, error) {
|
func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf iface.WGIface, initialNextHop Nexthop) (Nexthop, error) {
|
||||||
addr := prefix.Addr()
|
addr := prefix.Addr()
|
||||||
switch {
|
switch {
|
||||||
case addr.IsLoopback(),
|
case addr.IsLoopback(),
|
||||||
|
@ -59,6 +59,10 @@ service DaemonService {
|
|||||||
rpc SetNetworkMapPersistence(SetNetworkMapPersistenceRequest) returns (SetNetworkMapPersistenceResponse) {}
|
rpc SetNetworkMapPersistence(SetNetworkMapPersistenceRequest) returns (SetNetworkMapPersistenceResponse) {}
|
||||||
|
|
||||||
rpc TracePacket(TracePacketRequest) returns (TracePacketResponse) {}
|
rpc TracePacket(TracePacketRequest) returns (TracePacketResponse) {}
|
||||||
|
|
||||||
|
rpc SubscribeEvents(SubscribeRequest) returns (stream SystemEvent) {}
|
||||||
|
|
||||||
|
rpc GetEvents(GetEventsRequest) returns (GetEventsResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -116,6 +120,16 @@ message LoginRequest {
|
|||||||
optional bool disable_firewall = 23;
|
optional bool disable_firewall = 23;
|
||||||
|
|
||||||
optional bool block_lan_access = 24;
|
optional bool block_lan_access = 24;
|
||||||
|
|
||||||
|
optional bool disable_notifications = 25;
|
||||||
|
|
||||||
|
repeated string dns_labels = 26;
|
||||||
|
|
||||||
|
// cleanDNSLabels clean map list of DNS labels.
|
||||||
|
// This is needed because the generated code
|
||||||
|
// omits initialized empty slices due to omitempty tags
|
||||||
|
bool cleanDNSLabels = 27;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
@ -181,6 +195,8 @@ message GetConfigResponse {
|
|||||||
bool rosenpassEnabled = 11;
|
bool rosenpassEnabled = 11;
|
||||||
|
|
||||||
bool rosenpassPermissive = 12;
|
bool rosenpassPermissive = 12;
|
||||||
|
|
||||||
|
bool disable_notifications = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerState contains the latest state of a peer
|
// PeerState contains the latest state of a peer
|
||||||
@ -251,6 +267,8 @@ message FullStatus {
|
|||||||
repeated PeerState peers = 4;
|
repeated PeerState peers = 4;
|
||||||
repeated RelayState relays = 5;
|
repeated RelayState relays = 5;
|
||||||
repeated NSGroupState dns_servers = 6;
|
repeated NSGroupState dns_servers = 6;
|
||||||
|
|
||||||
|
repeated SystemEvent events = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListNetworksRequest {
|
message ListNetworksRequest {
|
||||||
@ -391,3 +409,35 @@ message TracePacketResponse {
|
|||||||
repeated TraceStage stages = 1;
|
repeated TraceStage stages = 1;
|
||||||
bool final_disposition = 2;
|
bool final_disposition = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SubscribeRequest{}
|
||||||
|
|
||||||
|
message SystemEvent {
|
||||||
|
enum Severity {
|
||||||
|
INFO = 0;
|
||||||
|
WARNING = 1;
|
||||||
|
ERROR = 2;
|
||||||
|
CRITICAL = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Category {
|
||||||
|
NETWORK = 0;
|
||||||
|
DNS = 1;
|
||||||
|
AUTHENTICATION = 2;
|
||||||
|
CONNECTIVITY = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
string id = 1;
|
||||||
|
Severity severity = 2;
|
||||||
|
Category category = 3;
|
||||||
|
string message = 4;
|
||||||
|
string userMessage = 5;
|
||||||
|
google.protobuf.Timestamp timestamp = 6;
|
||||||
|
map<string, string> metadata = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetEventsRequest {}
|
||||||
|
|
||||||
|
message GetEventsResponse {
|
||||||
|
repeated SystemEvent events = 1;
|
||||||
|
}
|
||||||
|
@ -52,6 +52,8 @@ type DaemonServiceClient interface {
|
|||||||
// SetNetworkMapPersistence enables or disables network map persistence
|
// SetNetworkMapPersistence enables or disables network map persistence
|
||||||
SetNetworkMapPersistence(ctx context.Context, in *SetNetworkMapPersistenceRequest, opts ...grpc.CallOption) (*SetNetworkMapPersistenceResponse, error)
|
SetNetworkMapPersistence(ctx context.Context, in *SetNetworkMapPersistenceRequest, opts ...grpc.CallOption) (*SetNetworkMapPersistenceResponse, error)
|
||||||
TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error)
|
TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error)
|
||||||
|
SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error)
|
||||||
|
GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type daemonServiceClient struct {
|
type daemonServiceClient struct {
|
||||||
@ -215,6 +217,47 @@ func (c *daemonServiceClient) TracePacket(ctx context.Context, in *TracePacketRe
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[0], "/daemon.DaemonService/SubscribeEvents", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &daemonServiceSubscribeEventsClient{stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DaemonService_SubscribeEventsClient interface {
|
||||||
|
Recv() (*SystemEvent, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type daemonServiceSubscribeEventsClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *daemonServiceSubscribeEventsClient) Recv() (*SystemEvent, error) {
|
||||||
|
m := new(SystemEvent)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) {
|
||||||
|
out := new(GetEventsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetEvents", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonServiceServer is the server API for DaemonService service.
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
// All implementations must embed UnimplementedDaemonServiceServer
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@ -253,6 +296,8 @@ type DaemonServiceServer interface {
|
|||||||
// SetNetworkMapPersistence enables or disables network map persistence
|
// SetNetworkMapPersistence enables or disables network map persistence
|
||||||
SetNetworkMapPersistence(context.Context, *SetNetworkMapPersistenceRequest) (*SetNetworkMapPersistenceResponse, error)
|
SetNetworkMapPersistence(context.Context, *SetNetworkMapPersistenceRequest) (*SetNetworkMapPersistenceResponse, error)
|
||||||
TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error)
|
TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error)
|
||||||
|
SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error
|
||||||
|
GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error)
|
||||||
mustEmbedUnimplementedDaemonServiceServer()
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +356,12 @@ func (UnimplementedDaemonServiceServer) SetNetworkMapPersistence(context.Context
|
|||||||
func (UnimplementedDaemonServiceServer) TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error) {
|
func (UnimplementedDaemonServiceServer) TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method TracePacket not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method TracePacket not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method SubscribeEvents not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
|
|
||||||
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@ -630,6 +681,45 @@ func _DaemonService_TracePacket_Handler(srv interface{}, ctx context.Context, de
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _DaemonService_SubscribeEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(SubscribeRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(DaemonServiceServer).SubscribeEvents(m, &daemonServiceSubscribeEventsServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type DaemonService_SubscribeEventsServer interface {
|
||||||
|
Send(*SystemEvent) error
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type daemonServiceSubscribeEventsServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *daemonServiceSubscribeEventsServer) Send(m *SystemEvent) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetEventsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).GetEvents(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/GetEvents",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).GetEvents(ctx, req.(*GetEventsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -705,7 +795,17 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "TracePacket",
|
MethodName: "TracePacket",
|
||||||
Handler: _DaemonService_TracePacket_Handler,
|
Handler: _DaemonService_TracePacket_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetEvents",
|
||||||
|
Handler: _DaemonService_GetEvents_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeEvents",
|
||||||
|
Handler: _DaemonService_SubscribeEvents_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "daemon.proto",
|
Metadata: "daemon.proto",
|
||||||
}
|
}
|
||||||
|
36
client/server/event.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) SubscribeEvents(req *proto.SubscribeRequest, stream proto.DaemonService_SubscribeEventsServer) error {
|
||||||
|
subscription := s.statusRecorder.SubscribeToEvents()
|
||||||
|
defer func() {
|
||||||
|
s.statusRecorder.UnsubscribeFromEvents(subscription)
|
||||||
|
log.Debug("client unsubscribed from events")
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Debug("client subscribed to events")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-subscription.Events():
|
||||||
|
if err := stream.Send(event); err != nil {
|
||||||
|
log.Warnf("error sending event to %v: %v", req, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-stream.Context().Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetEvents(context.Context, *proto.GetEventsRequest) (*proto.GetEventsResponse, error) {
|
||||||
|
events := s.statusRecorder.GetEventHistory()
|
||||||
|
return &proto.GetEventsResponse{Events: events}, nil
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
@ -404,6 +405,20 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
s.latestConfigInput.BlockLANAccess = msg.BlockLanAccess
|
s.latestConfigInput.BlockLANAccess = msg.BlockLanAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if msg.CleanDNSLabels {
|
||||||
|
inputConfig.DNSLabels = domain.List{}
|
||||||
|
s.latestConfigInput.DNSLabels = nil
|
||||||
|
} else if msg.DnsLabels != nil {
|
||||||
|
dnsLabels := domain.FromPunycodeList(msg.DnsLabels)
|
||||||
|
inputConfig.DNSLabels = dnsLabels
|
||||||
|
s.latestConfigInput.DNSLabels = dnsLabels
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.DisableNotifications != nil {
|
||||||
|
inputConfig.DisableNotifications = msg.DisableNotifications
|
||||||
|
s.latestConfigInput.DisableNotifications = msg.DisableNotifications
|
||||||
|
}
|
||||||
|
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if msg.OptionalPreSharedKey != nil {
|
if msg.OptionalPreSharedKey != nil {
|
||||||
@ -687,6 +702,7 @@ func (s *Server) Status(
|
|||||||
|
|
||||||
fullStatus := s.statusRecorder.GetFullStatus()
|
fullStatus := s.statusRecorder.GetFullStatus()
|
||||||
pbFullStatus := toProtoFullStatus(fullStatus)
|
pbFullStatus := toProtoFullStatus(fullStatus)
|
||||||
|
pbFullStatus.Events = s.statusRecorder.GetEventHistory()
|
||||||
statusResponse.FullStatus = pbFullStatus
|
statusResponse.FullStatus = pbFullStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,23 +752,25 @@ func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &proto.GetConfigResponse{
|
return &proto.GetConfigResponse{
|
||||||
ManagementUrl: managementURL,
|
ManagementUrl: managementURL,
|
||||||
ConfigFile: s.latestConfigInput.ConfigPath,
|
ConfigFile: s.latestConfigInput.ConfigPath,
|
||||||
LogFile: s.logFile,
|
LogFile: s.logFile,
|
||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
AdminURL: adminURL,
|
AdminURL: adminURL,
|
||||||
InterfaceName: s.config.WgIface,
|
InterfaceName: s.config.WgIface,
|
||||||
WireguardPort: int64(s.config.WgPort),
|
WireguardPort: int64(s.config.WgPort),
|
||||||
DisableAutoConnect: s.config.DisableAutoConnect,
|
DisableAutoConnect: s.config.DisableAutoConnect,
|
||||||
ServerSSHAllowed: *s.config.ServerSSHAllowed,
|
ServerSSHAllowed: *s.config.ServerSSHAllowed,
|
||||||
RosenpassEnabled: s.config.RosenpassEnabled,
|
RosenpassEnabled: s.config.RosenpassEnabled,
|
||||||
RosenpassPermissive: s.config.RosenpassPermissive,
|
RosenpassPermissive: s.config.RosenpassPermissive,
|
||||||
|
DisableNotifications: s.config.DisableNotifications,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) onSessionExpire() {
|
func (s *Server) onSessionExpire() {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
isUIActive := internal.CheckUIApp()
|
isUIActive := internal.CheckUIApp()
|
||||||
if !isUIActive {
|
if !isUIActive && !s.config.DisableNotifications {
|
||||||
if err := sendTerminalNotification(); err != nil {
|
if err := sendTerminalNotification(); err != nil {
|
||||||
log.Errorf("send session expire terminal notification: %v", err)
|
log.Errorf("send session expire terminal notification: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
}
|
}
|
||||||
|
|
||||||
secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay)
|
secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay)
|
||||||
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settings.NewManager(store), peersUpdateManager, secretsManager, nil, nil)
|
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settings.NewManager(store), peersUpdateManager, secretsManager, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/app"
|
"fyne.io/fyne/v2/app"
|
||||||
"fyne.io/fyne/v2/dialog"
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"fyne.io/systray"
|
"fyne.io/systray"
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
@ -33,6 +34,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/client/ui/event"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
@ -82,7 +84,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a := app.NewWithID("NetBird")
|
a := app.NewWithID("NetBird")
|
||||||
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
|
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnected))
|
||||||
|
|
||||||
if errorMSG != "" {
|
if errorMSG != "" {
|
||||||
showErrorMSG(errorMSG)
|
showErrorMSG(errorMSG)
|
||||||
@ -90,6 +92,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := newServiceClient(daemonAddr, a, showSettings, showRoutes)
|
client := newServiceClient(daemonAddr, a, showSettings, showRoutes)
|
||||||
|
settingsChangeChan := make(chan fyne.Settings)
|
||||||
|
a.Settings().AddChangeListener(settingsChangeChan)
|
||||||
|
go func() {
|
||||||
|
for range settingsChangeChan {
|
||||||
|
client.updateIcon()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if showSettings || showRoutes {
|
if showSettings || showRoutes {
|
||||||
a.Run()
|
a.Run()
|
||||||
} else {
|
} else {
|
||||||
@ -106,46 +116,36 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed netbird-systemtray-connected.ico
|
//go:embed netbird-systemtray-connected-macos.png
|
||||||
var iconConnectedICO []byte
|
var iconConnectedMacOS []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-connected.png
|
//go:embed netbird-systemtray-disconnected-macos.png
|
||||||
var iconConnectedPNG []byte
|
var iconDisconnectedMacOS []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-disconnected.ico
|
//go:embed netbird-systemtray-update-disconnected-macos.png
|
||||||
var iconDisconnectedICO []byte
|
var iconUpdateDisconnectedMacOS []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-disconnected.png
|
//go:embed netbird-systemtray-update-connected-macos.png
|
||||||
var iconDisconnectedPNG []byte
|
var iconUpdateConnectedMacOS []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-disconnected.ico
|
//go:embed netbird-systemtray-connecting-macos.png
|
||||||
var iconUpdateDisconnectedICO []byte
|
var iconConnectingMacOS []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-disconnected.png
|
//go:embed netbird-systemtray-error-macos.png
|
||||||
var iconUpdateDisconnectedPNG []byte
|
var iconErrorMacOS []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-connected.ico
|
|
||||||
var iconUpdateConnectedICO []byte
|
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-connected.png
|
|
||||||
var iconUpdateConnectedPNG []byte
|
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-cloud.ico
|
|
||||||
var iconUpdateCloudICO []byte
|
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-cloud.png
|
|
||||||
var iconUpdateCloudPNG []byte
|
|
||||||
|
|
||||||
type serviceClient struct {
|
type serviceClient struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
addr string
|
addr string
|
||||||
conn proto.DaemonServiceClient
|
conn proto.DaemonServiceClient
|
||||||
|
|
||||||
|
icAbout []byte
|
||||||
icConnected []byte
|
icConnected []byte
|
||||||
icDisconnected []byte
|
icDisconnected []byte
|
||||||
icUpdateConnected []byte
|
icUpdateConnected []byte
|
||||||
icUpdateDisconnected []byte
|
icUpdateDisconnected []byte
|
||||||
icUpdateCloud []byte
|
icConnecting []byte
|
||||||
|
icError []byte
|
||||||
|
|
||||||
// systray menu items
|
// systray menu items
|
||||||
mStatus *systray.MenuItem
|
mStatus *systray.MenuItem
|
||||||
@ -162,6 +162,7 @@ type serviceClient struct {
|
|||||||
mAllowSSH *systray.MenuItem
|
mAllowSSH *systray.MenuItem
|
||||||
mAutoConnect *systray.MenuItem
|
mAutoConnect *systray.MenuItem
|
||||||
mEnableRosenpass *systray.MenuItem
|
mEnableRosenpass *systray.MenuItem
|
||||||
|
mNotifications *systray.MenuItem
|
||||||
mAdvancedSettings *systray.MenuItem
|
mAdvancedSettings *systray.MenuItem
|
||||||
|
|
||||||
// application with main windows.
|
// application with main windows.
|
||||||
@ -197,6 +198,8 @@ type serviceClient struct {
|
|||||||
isUpdateIconActive bool
|
isUpdateIconActive bool
|
||||||
showRoutes bool
|
showRoutes bool
|
||||||
wRoutes fyne.Window
|
wRoutes fyne.Window
|
||||||
|
|
||||||
|
eventManager *event.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServiceClient instance constructor
|
// newServiceClient instance constructor
|
||||||
@ -214,20 +217,7 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes boo
|
|||||||
update: version.NewUpdate(),
|
update: version.NewUpdate(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
s.setNewIcons()
|
||||||
s.icConnected = iconConnectedICO
|
|
||||||
s.icDisconnected = iconDisconnectedICO
|
|
||||||
s.icUpdateConnected = iconUpdateConnectedICO
|
|
||||||
s.icUpdateDisconnected = iconUpdateDisconnectedICO
|
|
||||||
s.icUpdateCloud = iconUpdateCloudICO
|
|
||||||
|
|
||||||
} else {
|
|
||||||
s.icConnected = iconConnectedPNG
|
|
||||||
s.icDisconnected = iconDisconnectedPNG
|
|
||||||
s.icUpdateConnected = iconUpdateConnectedPNG
|
|
||||||
s.icUpdateDisconnected = iconUpdateDisconnectedPNG
|
|
||||||
s.icUpdateCloud = iconUpdateCloudPNG
|
|
||||||
}
|
|
||||||
|
|
||||||
if showSettings {
|
if showSettings {
|
||||||
s.showSettingsUI()
|
s.showSettingsUI()
|
||||||
@ -239,6 +229,44 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes boo
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) setNewIcons() {
|
||||||
|
s.icAbout = iconAbout
|
||||||
|
if s.app.Settings().ThemeVariant() == theme.VariantDark {
|
||||||
|
s.icConnected = iconConnectedDark
|
||||||
|
s.icDisconnected = iconDisconnected
|
||||||
|
s.icUpdateConnected = iconUpdateConnectedDark
|
||||||
|
s.icUpdateDisconnected = iconUpdateDisconnectedDark
|
||||||
|
s.icConnecting = iconConnectingDark
|
||||||
|
s.icError = iconErrorDark
|
||||||
|
} else {
|
||||||
|
s.icConnected = iconConnected
|
||||||
|
s.icDisconnected = iconDisconnected
|
||||||
|
s.icUpdateConnected = iconUpdateConnected
|
||||||
|
s.icUpdateDisconnected = iconUpdateDisconnected
|
||||||
|
s.icConnecting = iconConnecting
|
||||||
|
s.icError = iconError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) updateIcon() {
|
||||||
|
s.setNewIcons()
|
||||||
|
s.updateIndicationLock.Lock()
|
||||||
|
if s.connected {
|
||||||
|
if s.isUpdateIconActive {
|
||||||
|
systray.SetTemplateIcon(iconUpdateConnectedMacOS, s.icUpdateConnected)
|
||||||
|
} else {
|
||||||
|
systray.SetTemplateIcon(iconConnectedMacOS, s.icConnected)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if s.isUpdateIconActive {
|
||||||
|
systray.SetTemplateIcon(iconUpdateDisconnectedMacOS, s.icUpdateDisconnected)
|
||||||
|
} else {
|
||||||
|
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.updateIndicationLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) showSettingsUI() {
|
func (s *serviceClient) showSettingsUI() {
|
||||||
// add settings window UI elements.
|
// add settings window UI elements.
|
||||||
s.wSettings = s.app.NewWindow("NetBird Settings")
|
s.wSettings = s.app.NewWindow("NetBird Settings")
|
||||||
@ -376,8 +404,10 @@ func (s *serviceClient) login() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) menuUpClick() error {
|
func (s *serviceClient) menuUpClick() error {
|
||||||
|
systray.SetTemplateIcon(iconConnectingMacOS, s.icConnecting)
|
||||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
systray.SetTemplateIcon(iconErrorMacOS, s.icError)
|
||||||
log.Errorf("get client: %v", err)
|
log.Errorf("get client: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -403,10 +433,12 @@ func (s *serviceClient) menuUpClick() error {
|
|||||||
log.Errorf("up service: %v", err)
|
log.Errorf("up service: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) menuDownClick() error {
|
func (s *serviceClient) menuDownClick() error {
|
||||||
|
systray.SetTemplateIcon(iconConnectingMacOS, s.icConnecting)
|
||||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("get client: %v", err)
|
log.Errorf("get client: %v", err)
|
||||||
@ -458,9 +490,9 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
s.connected = true
|
s.connected = true
|
||||||
s.sendNotification = true
|
s.sendNotification = true
|
||||||
if s.isUpdateIconActive {
|
if s.isUpdateIconActive {
|
||||||
systray.SetIcon(s.icUpdateConnected)
|
systray.SetTemplateIcon(iconUpdateConnectedMacOS, s.icUpdateConnected)
|
||||||
} else {
|
} else {
|
||||||
systray.SetIcon(s.icConnected)
|
systray.SetTemplateIcon(iconConnectedMacOS, s.icConnected)
|
||||||
}
|
}
|
||||||
systray.SetTooltip("NetBird (Connected)")
|
systray.SetTooltip("NetBird (Connected)")
|
||||||
s.mStatus.SetTitle("Connected")
|
s.mStatus.SetTitle("Connected")
|
||||||
@ -482,11 +514,9 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
s.isUpdateIconActive = s.update.SetDaemonVersion(status.DaemonVersion)
|
s.isUpdateIconActive = s.update.SetDaemonVersion(status.DaemonVersion)
|
||||||
if !s.isUpdateIconActive {
|
if !s.isUpdateIconActive {
|
||||||
if systrayIconState {
|
if systrayIconState {
|
||||||
systray.SetIcon(s.icConnected)
|
systray.SetTemplateIcon(iconConnectedMacOS, s.icConnected)
|
||||||
s.mAbout.SetIcon(s.icConnected)
|
|
||||||
} else {
|
} else {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
|
||||||
s.mAbout.SetIcon(s.icDisconnected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,7 +536,6 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -517,9 +546,9 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
func (s *serviceClient) setDisconnectedStatus() {
|
func (s *serviceClient) setDisconnectedStatus() {
|
||||||
s.connected = false
|
s.connected = false
|
||||||
if s.isUpdateIconActive {
|
if s.isUpdateIconActive {
|
||||||
systray.SetIcon(s.icUpdateDisconnected)
|
systray.SetTemplateIcon(iconUpdateDisconnectedMacOS, s.icUpdateDisconnected)
|
||||||
} else {
|
} else {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
|
||||||
}
|
}
|
||||||
systray.SetTooltip("NetBird (Disconnected)")
|
systray.SetTooltip("NetBird (Disconnected)")
|
||||||
s.mStatus.SetTitle("Disconnected")
|
s.mStatus.SetTitle("Disconnected")
|
||||||
@ -529,7 +558,7 @@ func (s *serviceClient) setDisconnectedStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) onTrayReady() {
|
func (s *serviceClient) onTrayReady() {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
|
||||||
systray.SetTooltip("NetBird")
|
systray.SetTooltip("NetBird")
|
||||||
|
|
||||||
// setup systray menu items
|
// setup systray menu items
|
||||||
@ -546,6 +575,7 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
s.mAllowSSH = s.mSettings.AddSubMenuItemCheckbox("Allow SSH", "Allow SSH connections", false)
|
s.mAllowSSH = s.mSettings.AddSubMenuItemCheckbox("Allow SSH", "Allow SSH connections", false)
|
||||||
s.mAutoConnect = s.mSettings.AddSubMenuItemCheckbox("Connect on Startup", "Connect automatically when the service starts", false)
|
s.mAutoConnect = s.mSettings.AddSubMenuItemCheckbox("Connect on Startup", "Connect automatically when the service starts", false)
|
||||||
s.mEnableRosenpass = s.mSettings.AddSubMenuItemCheckbox("Enable Quantum-Resistance", "Enable post-quantum security via Rosenpass", false)
|
s.mEnableRosenpass = s.mSettings.AddSubMenuItemCheckbox("Enable Quantum-Resistance", "Enable post-quantum security via Rosenpass", false)
|
||||||
|
s.mNotifications = s.mSettings.AddSubMenuItemCheckbox("Notifications", "Enable notifications", true)
|
||||||
s.mAdvancedSettings = s.mSettings.AddSubMenuItem("Advanced Settings", "Advanced settings of the application")
|
s.mAdvancedSettings = s.mSettings.AddSubMenuItem("Advanced Settings", "Advanced settings of the application")
|
||||||
s.loadSettings()
|
s.loadSettings()
|
||||||
|
|
||||||
@ -554,7 +584,7 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
|
|
||||||
s.mAbout = systray.AddMenuItem("About", "About")
|
s.mAbout = systray.AddMenuItem("About", "About")
|
||||||
s.mAbout.SetIcon(s.icDisconnected)
|
s.mAbout.SetIcon(s.icAbout)
|
||||||
versionString := normalizedVersion(version.NetbirdVersion())
|
versionString := normalizedVersion(version.NetbirdVersion())
|
||||||
s.mVersionUI = s.mAbout.AddSubMenuItem(fmt.Sprintf("GUI: %s", versionString), fmt.Sprintf("GUI Version: %s", versionString))
|
s.mVersionUI = s.mAbout.AddSubMenuItem(fmt.Sprintf("GUI: %s", versionString), fmt.Sprintf("GUI Version: %s", versionString))
|
||||||
s.mVersionUI.Disable()
|
s.mVersionUI.Disable()
|
||||||
@ -582,6 +612,10 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
s.eventManager = event.NewManager(s.app, s.addr)
|
||||||
|
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||||
|
go s.eventManager.Start(s.ctx)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
@ -616,7 +650,6 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
}
|
}
|
||||||
if err := s.updateConfig(); err != nil {
|
if err := s.updateConfig(); err != nil {
|
||||||
log.Errorf("failed to update config: %v", err)
|
log.Errorf("failed to update config: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
case <-s.mAutoConnect.ClickedCh:
|
case <-s.mAutoConnect.ClickedCh:
|
||||||
if s.mAutoConnect.Checked() {
|
if s.mAutoConnect.Checked() {
|
||||||
@ -626,7 +659,6 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
}
|
}
|
||||||
if err := s.updateConfig(); err != nil {
|
if err := s.updateConfig(); err != nil {
|
||||||
log.Errorf("failed to update config: %v", err)
|
log.Errorf("failed to update config: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
case <-s.mEnableRosenpass.ClickedCh:
|
case <-s.mEnableRosenpass.ClickedCh:
|
||||||
if s.mEnableRosenpass.Checked() {
|
if s.mEnableRosenpass.Checked() {
|
||||||
@ -636,7 +668,6 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
}
|
}
|
||||||
if err := s.updateConfig(); err != nil {
|
if err := s.updateConfig(); err != nil {
|
||||||
log.Errorf("failed to update config: %v", err)
|
log.Errorf("failed to update config: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
case <-s.mAdvancedSettings.ClickedCh:
|
case <-s.mAdvancedSettings.ClickedCh:
|
||||||
s.mAdvancedSettings.Disable()
|
s.mAdvancedSettings.Disable()
|
||||||
@ -659,7 +690,20 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
defer s.mRoutes.Enable()
|
defer s.mRoutes.Enable()
|
||||||
s.runSelfCommand("networks", "true")
|
s.runSelfCommand("networks", "true")
|
||||||
}()
|
}()
|
||||||
|
case <-s.mNotifications.ClickedCh:
|
||||||
|
if s.mNotifications.Checked() {
|
||||||
|
s.mNotifications.Uncheck()
|
||||||
|
} else {
|
||||||
|
s.mNotifications.Check()
|
||||||
|
}
|
||||||
|
if s.eventManager != nil {
|
||||||
|
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||||
|
}
|
||||||
|
if err := s.updateConfig(); err != nil {
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("process connection: %v", err)
|
log.Errorf("process connection: %v", err)
|
||||||
}
|
}
|
||||||
@ -759,8 +803,20 @@ func (s *serviceClient) getSrvConfig() {
|
|||||||
if !cfg.RosenpassEnabled {
|
if !cfg.RosenpassEnabled {
|
||||||
s.sRosenpassPermissive.Disable()
|
s.sRosenpassPermissive.Disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.mNotifications == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.DisableNotifications {
|
||||||
|
s.mNotifications.Uncheck()
|
||||||
|
} else {
|
||||||
|
s.mNotifications.Check()
|
||||||
|
}
|
||||||
|
if s.eventManager != nil {
|
||||||
|
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) onUpdateAvailable() {
|
func (s *serviceClient) onUpdateAvailable() {
|
||||||
@ -771,9 +827,9 @@ func (s *serviceClient) onUpdateAvailable() {
|
|||||||
s.isUpdateIconActive = true
|
s.isUpdateIconActive = true
|
||||||
|
|
||||||
if s.connected {
|
if s.connected {
|
||||||
systray.SetIcon(s.icUpdateConnected)
|
systray.SetTemplateIcon(iconUpdateConnectedMacOS, s.icUpdateConnected)
|
||||||
} else {
|
} else {
|
||||||
systray.SetIcon(s.icUpdateDisconnected)
|
systray.SetTemplateIcon(iconUpdateDisconnectedMacOS, s.icUpdateDisconnected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,6 +881,15 @@ func (s *serviceClient) loadSettings() {
|
|||||||
} else {
|
} else {
|
||||||
s.mEnableRosenpass.Uncheck()
|
s.mEnableRosenpass.Uncheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.DisableNotifications {
|
||||||
|
s.mNotifications.Uncheck()
|
||||||
|
} else {
|
||||||
|
s.mNotifications.Check()
|
||||||
|
}
|
||||||
|
if s.eventManager != nil {
|
||||||
|
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateConfig updates the configuration parameters
|
// updateConfig updates the configuration parameters
|
||||||
@ -833,12 +898,14 @@ func (s *serviceClient) updateConfig() error {
|
|||||||
disableAutoStart := !s.mAutoConnect.Checked()
|
disableAutoStart := !s.mAutoConnect.Checked()
|
||||||
sshAllowed := s.mAllowSSH.Checked()
|
sshAllowed := s.mAllowSSH.Checked()
|
||||||
rosenpassEnabled := s.mEnableRosenpass.Checked()
|
rosenpassEnabled := s.mEnableRosenpass.Checked()
|
||||||
|
notificationsDisabled := !s.mNotifications.Checked()
|
||||||
|
|
||||||
loginRequest := proto.LoginRequest{
|
loginRequest := proto.LoginRequest{
|
||||||
IsLinuxDesktopClient: runtime.GOOS == "linux",
|
IsLinuxDesktopClient: runtime.GOOS == "linux",
|
||||||
ServerSSHAllowed: &sshAllowed,
|
ServerSSHAllowed: &sshAllowed,
|
||||||
RosenpassEnabled: &rosenpassEnabled,
|
RosenpassEnabled: &rosenpassEnabled,
|
||||||
DisableAutoConnect: &disableAutoStart,
|
DisableAutoConnect: &disableAutoStart,
|
||||||
|
DisableNotifications: ¬ificationsDisabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.restartClient(&loginRequest); err != nil {
|
if err := s.restartClient(&loginRequest); err != nil {
|
||||||
@ -851,17 +918,20 @@ func (s *serviceClient) updateConfig() error {
|
|||||||
|
|
||||||
// restartClient restarts the client connection.
|
// restartClient restarts the client connection.
|
||||||
func (s *serviceClient) restartClient(loginRequest *proto.LoginRequest) error {
|
func (s *serviceClient) restartClient(loginRequest *proto.LoginRequest) error {
|
||||||
|
ctx, cancel := context.WithTimeout(s.ctx, defaultFailTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
client, err := s.getSrvClient(failFastTimeout)
|
client, err := s.getSrvClient(failFastTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.Login(s.ctx, loginRequest)
|
_, err = client.Login(ctx, loginRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.Up(s.ctx, &proto.UpRequest{})
|
_, err = client.Up(ctx, &proto.UpRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
151
client/ui/event/event.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
app fyne.App
|
||||||
|
addr string
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(app fyne.App, addr string) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
app: app,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) Start(ctx context.Context) {
|
||||||
|
e.mu.Lock()
|
||||||
|
e.ctx, e.cancel = context.WithCancel(ctx)
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
expBackOff := backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
|
InitialInterval: time.Second,
|
||||||
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
|
MaxInterval: 10 * time.Second,
|
||||||
|
MaxElapsedTime: 0,
|
||||||
|
Stop: backoff.Stop,
|
||||||
|
Clock: backoff.SystemClock,
|
||||||
|
}, ctx)
|
||||||
|
|
||||||
|
if err := backoff.Retry(e.streamEvents, expBackOff); err != nil {
|
||||||
|
log.Errorf("event stream ended: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) streamEvents() error {
|
||||||
|
e.mu.Lock()
|
||||||
|
ctx := e.ctx
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
client, err := getClient(e.addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := client.SubscribeEvents(ctx, &proto.SubscribeRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to subscribe to events: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("subscribed to daemon events")
|
||||||
|
defer func() {
|
||||||
|
log.Info("unsubscribed from daemon events")
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
event, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error receiving event: %w", err)
|
||||||
|
}
|
||||||
|
e.handleEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) Stop() {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
if e.cancel != nil {
|
||||||
|
e.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) SetNotificationsEnabled(enabled bool) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
e.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) handleEvent(event *proto.SystemEvent) {
|
||||||
|
e.mu.Lock()
|
||||||
|
enabled := e.enabled
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
if !enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
title := e.getEventTitle(event)
|
||||||
|
e.app.SendNotification(fyne.NewNotification(title, event.UserMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) getEventTitle(event *proto.SystemEvent) string {
|
||||||
|
var prefix string
|
||||||
|
switch event.Severity {
|
||||||
|
case proto.SystemEvent_ERROR, proto.SystemEvent_CRITICAL:
|
||||||
|
prefix = "Error"
|
||||||
|
case proto.SystemEvent_WARNING:
|
||||||
|
prefix = "Warning"
|
||||||
|
default:
|
||||||
|
prefix = "Info"
|
||||||
|
}
|
||||||
|
|
||||||
|
var category string
|
||||||
|
switch event.Category {
|
||||||
|
case proto.SystemEvent_DNS:
|
||||||
|
category = "DNS"
|
||||||
|
case proto.SystemEvent_NETWORK:
|
||||||
|
category = "Network"
|
||||||
|
case proto.SystemEvent_AUTHENTICATION:
|
||||||
|
category = "Authentication"
|
||||||
|
case proto.SystemEvent_CONNECTIVITY:
|
||||||
|
category = "Connectivity"
|
||||||
|
default:
|
||||||
|
category = "System"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s: %s", prefix, category)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClient(addr string) (proto.DaemonServiceClient, error) {
|
||||||
|
conn, err := grpc.NewClient(
|
||||||
|
strings.TrimPrefix(addr, "tcp://"),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithUserAgent(system.GetDesktopUIUserAgent()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return proto.NewDaemonServiceClient(conn), nil
|
||||||
|
}
|
43
client/ui/icons.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//go:build !(linux && 386) && !windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed netbird.png
|
||||||
|
var iconAbout []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connected.png
|
||||||
|
var iconConnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connected-dark.png
|
||||||
|
var iconConnectedDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-disconnected.png
|
||||||
|
var iconDisconnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-disconnected.png
|
||||||
|
var iconUpdateDisconnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-disconnected-dark.png
|
||||||
|
var iconUpdateDisconnectedDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-connected.png
|
||||||
|
var iconUpdateConnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-connected-dark.png
|
||||||
|
var iconUpdateConnectedDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connecting.png
|
||||||
|
var iconConnecting []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connecting-dark.png
|
||||||
|
var iconConnectingDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-error.png
|
||||||
|
var iconError []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-error-dark.png
|
||||||
|
var iconErrorDark []byte
|
41
client/ui/icons_windows.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed netbird.ico
|
||||||
|
var iconAbout []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connected.ico
|
||||||
|
var iconConnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connected-dark.ico
|
||||||
|
var iconConnectedDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-disconnected.ico
|
||||||
|
var iconDisconnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-disconnected.ico
|
||||||
|
var iconUpdateDisconnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-disconnected-dark.ico
|
||||||
|
var iconUpdateDisconnectedDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-connected.ico
|
||||||
|
var iconUpdateConnected []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-connected-dark.ico
|
||||||
|
var iconUpdateConnectedDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connecting.ico
|
||||||
|
var iconConnecting []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-connecting-dark.ico
|
||||||
|
var iconConnectingDark []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-error.ico
|
||||||
|
var iconError []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-error-dark.ico
|
||||||
|
var iconErrorDark []byte
|
BIN
client/ui/netbird-systemtray-connected-dark.ico
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/netbird-systemtray-connected-dark.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
client/ui/netbird-systemtray-connected-macos.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 5.2 KiB |
BIN
client/ui/netbird-systemtray-connecting-dark.ico
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/netbird-systemtray-connecting-dark.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
client/ui/netbird-systemtray-connecting-macos.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
client/ui/netbird-systemtray-connecting.ico
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/netbird-systemtray-connecting.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
client/ui/netbird-systemtray-disconnected-macos.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
client/ui/netbird-systemtray-error-dark.ico
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/netbird-systemtray-error-dark.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
client/ui/netbird-systemtray-error-macos.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
client/ui/netbird-systemtray-error.ico
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/netbird-systemtray-error.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 5.5 KiB |
BIN
client/ui/netbird-systemtray-update-connected-dark.ico
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
client/ui/netbird-systemtray-update-connected-dark.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
client/ui/netbird-systemtray-update-connected-macos.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
client/ui/netbird-systemtray-update-disconnected-dark.ico
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/netbird-systemtray-update-disconnected-dark.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
client/ui/netbird-systemtray-update-disconnected-macos.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 103 KiB |