Compare commits

...

10 Commits

Author SHA1 Message Date
Zoltan Papp
0ab0b834eb Fix 2024-12-19 14:01:52 +01:00
Zoltán Papp
9ff03141ba Add peer conn profile code 2024-12-19 13:53:51 +01:00
Maycon Santos
37ad370344 [client] Avoid using iota on mixed const block (#3057)
Used the values as resolved when the first iota value was the second const in the block.
2024-12-16 18:09:31 +01:00
VYSE V.E.O
703647da1e fix client unsupported h2 protocol when only 443 activated (#3009)
When I remove 80 http port in Caddyfile, netbird client cannot connect server:443. Logs show error below:
{"level":"debug","ts":1733809631.4012625,"logger":"http.stdlib","msg":"http: TLS handshake error from redacted:41580: tls: client requested unsupported application protocols ([h2])"}
I wonder here h2 protocol is absent.
2024-12-16 14:17:46 +01:00
Maycon Santos
9eff58ae62 Upgrade x/crypto package (#3055)
Mitigates the CVE-2024-45337
2024-12-16 10:30:41 +01:00
Jesse R Codling
3844516aa7 [client] fix: reformat IPv6 ICE addresses when punching (#3050)
Should fix #2327 and #2606 by checking for IPv6 addresses from ICE
2024-12-16 09:58:54 +01:00
M. Essam
f591e47404 Handle DNF5 install script (#3026) 2024-12-16 09:41:36 +01:00
Maycon Santos
287ae81195 [misc] split tests with management and rest (#3051)
optimize go cache for tests
2024-12-14 21:18:46 +01:00
M. Essam
a4a30744ad Fix race condition with systray ready (#2993) 2024-12-14 12:17:53 -08:00
Maycon Santos
dcba6a6b7e fix: client/Dockerfile to reduce vulnerabilities (#3019)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-8235201
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-8235201

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-12-11 16:46:51 +01:00
23 changed files with 618 additions and 62 deletions

View File

@@ -21,6 +21,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: "1.23.x"
cache: false
- name: Checkout code
uses: actions/checkout@v4
@@ -28,8 +29,9 @@ jobs:
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: macos-go-${{ hashFiles('**/go.sum') }}
key: macos-gotest-${{ hashFiles('**/go.sum') }}
restore-keys: |
macos-gotest-
macos-go-
- name: Install libpcap
@@ -42,4 +44,4 @@ jobs:
run: git --no-pager diff --exit-code
- name: Test
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 ./...
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 $(go list ./... | grep -v /management)

View File

@@ -11,7 +11,133 @@ concurrency:
cancel-in-progress: true
jobs:
build-cache:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.23.x"
cache: false
- 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@v4
id: cache
with:
path: |
${{ env.cache }}
${{ env.modcache }}
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
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: steps.cache.outputs.cache-hit != 'true'
run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386
- name: Build client
if: steps.cache.outputs.cache-hit != 'true'
working-directory: client
run: CGO_ENABLED=1 go build .
- name: Build client 386
if: steps.cache.outputs.cache-hit != 'true'
working-directory: client
run: CGO_ENABLED=1 GOARCH=386 go build -o client-386 .
- name: Build management
if: steps.cache.outputs.cache-hit != 'true'
working-directory: management
run: CGO_ENABLED=1 go build .
- name: Build management 386
if: steps.cache.outputs.cache-hit != 'true'
working-directory: management
run: CGO_ENABLED=1 GOARCH=386 go build -o management-386 .
- name: Build signal
if: steps.cache.outputs.cache-hit != 'true'
working-directory: signal
run: CGO_ENABLED=1 go build .
- name: Build signal 386
if: steps.cache.outputs.cache-hit != 'true'
working-directory: signal
run: CGO_ENABLED=1 GOARCH=386 go build -o signal-386 .
- name: Build relay
if: steps.cache.outputs.cache-hit != 'true'
working-directory: relay
run: CGO_ENABLED=1 go build .
- name: Build relay 386
if: steps.cache.outputs.cache-hit != 'true'
working-directory: relay
run: CGO_ENABLED=1 GOARCH=386 go build -o relay-386 .
test:
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 }} CI=true go test -exec 'sudo' -timeout 10m -p 1 $(go list ./... | grep -v /management)
test_management:
needs: [ build-cache ]
strategy:
fail-fast: false
matrix:
@@ -23,19 +149,26 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: "1.23.x"
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
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
@@ -50,9 +183,10 @@ jobs:
run: git --no-pager diff --exit-code
- name: Test
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 10m -p 1 ./...
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 10m $(go list ./... | grep /management)
benchmark:
needs: [ build-cache ]
strategy:
fail-fast: false
matrix:
@@ -64,19 +198,26 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: "1.23.x"
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
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
@@ -91,27 +232,36 @@ jobs:
run: git --no-pager diff --exit-code
- name: Test
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -run=^$ -bench=. -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 10m -p 1 ./...
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -run=^$ -bench=. -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 10m ./...
test_client_on_docker:
needs: [ build-cache ]
runs-on: ubuntu-20.04
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.23.x"
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
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

View File

@@ -24,6 +24,23 @@ jobs:
id: go
with:
go-version: "1.23.x"
cache: false
- name: Get Go environment
run: |
echo "cache=$(go env GOCACHE)" >> $env:GITHUB_ENV
echo "modcache=$(go env GOMODCACHE)" >> $env:GITHUB_ENV
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
${{ env.cache }}
${{ env.modcache }}
key: ${{ runner.os }}-gotest-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-gotest-
${{ runner.os }}-go-
- name: Download wintun
uses: carlosperate/download-file-action@v2
@@ -42,11 +59,13 @@ jobs:
- run: choco install -y sysinternals --ignore-checksums
- run: choco install -y mingw
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOMODCACHE=C:\Users\runneradmin\go\pkg\mod
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=C:\Users\runneradmin\AppData\Local\go-build
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOMODCACHE=${{ env.cache }}
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=${{ env.modcache }}
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe mod tidy
- run: echo "files=$(go list ./... | ForEach-Object { $_ } | Where-Object { $_ -notmatch '/management' })" >> $env:GITHUB_ENV
- name: test
run: PsExec64 -s -w ${{ github.workspace }} cmd.exe /c "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe test -timeout 10m -p 1 ./... > test-out.txt 2>&1"
run: PsExec64 -s -w ${{ github.workspace }} cmd.exe /c "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe test -timeout 10m -p 1 ${{ env.files }} > test-out.txt 2>&1"
- name: test output
if: ${{ always() }}
run: Get-Content test-out.txt

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21.0
RUN apk add --no-cache ca-certificates iptables ip6tables
ENV NB_FOREGROUND_MODE=true
ENTRYPOINT [ "/usr/local/bin/netbird","up"]

View File

@@ -218,3 +218,31 @@ func (c *KernelConfigurer) GetStats(peerKey string) (WGStats, error) {
RxBytes: peer.ReceiveBytes,
}, nil
}
func (c *KernelConfigurer) GetAllStat() (map[string]WGStats, error) {
wg, err := wgctrl.New()
if err != nil {
return nil, fmt.Errorf("wgctl: %w", err)
}
defer func() {
err = wg.Close()
if err != nil {
log.Errorf("Got error while closing wgctl: %v", err)
}
}()
wgDevice, err := wg.Device(c.deviceName)
if err != nil {
return nil, fmt.Errorf("get device %s: %w", c.deviceName, err)
}
stats := make(map[string]WGStats)
for _, peer := range wgDevice.Peers {
stats[peer.PublicKey.String()] = WGStats{
LastHandshake: peer.LastHandshakeTime,
TxBytes: peer.TransmitBytes,
RxBytes: peer.ReceiveBytes,
}
}
return stats, nil
}

View File

@@ -263,6 +263,52 @@ func (t *WGUSPConfigurer) GetStats(peerKey string) (WGStats, error) {
}, nil
}
func (t *WGUSPConfigurer) GetAllStat() (map[string]WGStats, error) {
ipc, err := t.device.IpcGet()
if err != nil {
return nil, fmt.Errorf("ipc get: %w", err)
}
stats, err := parsePeerInfo(ipc, []string{
"last_handshake_time_sec",
"last_handshake_time_nsec",
"tx_bytes",
"rx_bytes",
})
if err != nil {
return nil, fmt.Errorf("find peer info: %w", err)
}
wgStats := make(map[string]WGStats)
for k, v := range stats {
sec, err := strconv.ParseInt(v["last_handshake_time_sec"], 10, 64)
if err != nil {
return nil, fmt.Errorf("parse handshake sec: %w", err)
}
nsec, err := strconv.ParseInt(v["last_handshake_time_nsec"], 10, 64)
if err != nil {
return nil, fmt.Errorf("parse handshake nsec: %w", err)
}
txBytes, err := strconv.ParseInt(v["tx_bytes"], 10, 64)
if err != nil {
return nil, fmt.Errorf("parse tx_bytes: %w", err)
}
rxBytes, err := strconv.ParseInt(v["rx_bytes"], 10, 64)
if err != nil {
return nil, fmt.Errorf("parse rx_bytes: %w", err)
}
wgStats[k] = WGStats{
LastHandshake: time.Unix(sec, nsec),
TxBytes: txBytes,
RxBytes: rxBytes,
}
}
return wgStats, nil
}
func findPeerInfo(ipcInput string, peerKey string, searchConfigKeys []string) (map[string]string, error) {
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
@@ -310,6 +356,44 @@ func findPeerInfo(ipcInput string, peerKey string, searchConfigKeys []string) (m
return configFound, nil
}
func parsePeerInfo(ipcInput string, searchConfigKeys []string) (map[string]map[string]string, error) {
lines := strings.Split(ipcInput, "\n")
allPeers := map[string]map[string]string{}
var currentPeerKey string
for _, line := range lines {
line = strings.TrimSpace(line)
// Detect new peer section by public key
if strings.HasPrefix(line, "public_key=") {
hexKey := strings.TrimPrefix(line, "public_key=")
keyBytes, _ := hex.DecodeString(hexKey)
wgKey, _ := wgtypes.NewKey(keyBytes)
currentPeerKey = wgKey.String()
if _, exists := allPeers[currentPeerKey]; !exists {
allPeers[currentPeerKey] = map[string]string{}
}
continue
}
// Parse configuration keys for the current peer
if currentPeerKey != "" {
for _, key := range searchConfigKeys {
if strings.HasPrefix(line, key+"=") {
v := strings.SplitN(line, "=", 2)
if len(v) == 2 {
allPeers[currentPeerKey][v[0]] = v[1]
}
}
}
}
}
return allPeers, nil
}
func toWgUserspaceString(wgCfg wgtypes.Config) string {
var sb strings.Builder
if wgCfg.PrivateKey != nil {

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -34,6 +35,19 @@ errno=0
`
func Test_parsePeerInto(t *testing.T) {
r, err := parsePeerInfo(ipcFixture, []string{
"last_handshake_time_sec",
"last_handshake_time_nsec",
"tx_bytes",
"rx_bytes",
})
if err != nil {
t.Errorf("parsePeerInfo() error = %v", err)
}
log.Infof("r: %v", r)
}
func Test_findPeerInfo(t *testing.T) {
tests := []struct {
name string

View File

@@ -17,4 +17,5 @@ type WGConfigurer interface {
RemoveAllowedIP(peerKey string, allowedIP string) error
Close()
GetStats(peerKey string) (configurer.WGStats, error)
GetAllStat() (map[string]configurer.WGStats, error)
}

View File

@@ -27,14 +27,14 @@ import (
type status int
const (
defaultModuleDir = "/lib/modules"
unknown status = iota
unloaded
unloading
loading
live
inuse
envDisableWireGuardKernel = "NB_WG_KERNEL_DISABLED"
unknown status = 1
unloaded status = 2
unloading status = 3
loading status = 4
live status = 5
inuse status = 6
defaultModuleDir = "/lib/modules"
envDisableWireGuardKernel = "NB_WG_KERNEL_DISABLED"
)
type module struct {

View File

@@ -16,6 +16,7 @@ import (
"github.com/netbirdio/netbird/client/iface/configurer"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/wgproxy"
"github.com/netbirdio/netbird/connprofile"
)
const (
@@ -114,7 +115,13 @@ func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.D
defer w.mu.Unlock()
log.Debugf("updating interface %s peer %s, endpoint %s", w.tun.DeviceName(), peerKey, endpoint)
return w.configurer.UpdatePeer(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
err := w.configurer.UpdatePeer(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
if err != nil {
return err
}
connprofile.Profiler.WireGuardConfigured(peerKey)
return nil
}
// RemovePeer removes a Wireguard Peer from the interface iface
@@ -208,6 +215,10 @@ func (w *WGIface) GetStats(peerKey string) (configurer.WGStats, error) {
return w.configurer.GetStats(peerKey)
}
func (w *WGIface) GetAllStat() (map[string]configurer.WGStats, error) {
return w.configurer.GetAllStat()
}
func (w *WGIface) waitUntilRemoved() error {
maxWaitTime := 5 * time.Second
timeout := time.NewTimer(maxWaitTime)

View File

@@ -33,4 +33,5 @@ type IWGIface interface {
GetFilter() device.PacketFilter
GetDevice() *device.FilteredDevice
GetStats(peerKey string) (configurer.WGStats, error)
GetAllStat() (map[string]configurer.WGStats, error)
}

View File

@@ -39,6 +39,7 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager"
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
"github.com/netbirdio/netbird/client/internal/statemanager"
"github.com/netbirdio/netbird/connprofile"
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
nbssh "github.com/netbirdio/netbird/client/ssh"
@@ -420,6 +421,8 @@ func (e *Engine) Start() error {
return fmt.Errorf("up wg interface: %w", err)
}
connprofile.Profiler.WGInterfaceUP(e.wgInterface)
if e.firewall != nil {
e.acl = acl.NewDefaultManager(e.firewall)
}
@@ -786,7 +789,6 @@ func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
}
func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
// intentionally leave it before checking serial because for now it can happen that peer IP changed but serial didn't
if networkMap.GetPeerConfig() != nil {
err := e.updateConfig(networkMap.GetPeerConfig())
@@ -821,6 +823,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
e.clientRoutesMu.Unlock()
log.Debugf("got peers update from Management Service, total peers to connect to = %d", len(networkMap.GetRemotePeers()))
connprofile.Profiler.NetworkMapUpdate(networkMap.GetRemotePeers())
e.updateOfflinePeers(networkMap.GetOfflinePeers())
@@ -1105,6 +1108,7 @@ func (e *Engine) receiveSignalEvents() {
RosenpassAddr: rosenpassAddr,
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
})
connprofile.Profiler.OfferAnswerReceived(msg.Key)
case sProto.Body_ANSWER:
remoteCred, err := signal.UnMarshalCredential(msg)
if err != nil {
@@ -1128,6 +1132,7 @@ func (e *Engine) receiveSignalEvents() {
RosenpassAddr: rosenpassAddr,
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
})
connprofile.Profiler.OfferAnswerReceived(msg.Key)
case sProto.Body_CANDIDATE:
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"github.com/pion/ice/v3"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"github.com/netbirdio/netbird/connprofile"
signal "github.com/netbirdio/netbird/signal/client"
sProto "github.com/netbirdio/netbird/signal/proto"
)
@@ -66,5 +67,6 @@ func (s *Signaler) signalOfferAnswer(offerAnswer OfferAnswer, remoteKey string,
return err
}
connprofile.Profiler.OfferSent(remoteKey)
return nil
}

View File

@@ -264,7 +264,13 @@ func (w *WorkerICE) closeAgent(cancel context.CancelFunc) {
func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) {
// wait local endpoint configuration
time.Sleep(time.Second)
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pair.Remote.Address(), remoteWgPort))
addrString := pair.Remote.Address()
parsed, err := netip.ParseAddr(addrString)
if (err == nil) && (parsed.Is6()) {
addrString = fmt.Sprintf("[%s]", addrString)
//IPv6 Literals need to be wrapped in brackets for Resolve*Addr()
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addrString, remoteWgPort))
if err != nil {
w.log.Warnf("got an error while resolving the udp address, err: %s", err)
return

View File

@@ -572,6 +572,7 @@ func (s *serviceClient) onTrayReady() {
s.update.SetOnUpdateListener(s.onUpdateAvailable)
go func() {
s.getSrvConfig()
time.Sleep(100 * time.Millisecond) // To prevent race condition caused by systray not being fully initialized and ignoring setIcon
for {
err := s.updateStatus()
if err != nil {

7
connprofile/iface.go Normal file
View File

@@ -0,0 +1,7 @@
package connprofile
import "github.com/netbirdio/netbird/client/iface/configurer"
type wgIface interface {
GetAllStat() (map[string]configurer.WGStats, error)
}

164
connprofile/profiler.go Normal file
View File

@@ -0,0 +1,164 @@
package connprofile
import (
"sync"
"time"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/proto"
)
type Profile struct {
NetworkMapUpdate time.Time
OfferSent time.Time
OfferReceived time.Time
WireGuardConfigured time.Time
WireGuardConnected time.Time
}
type ConnProfiler struct {
profiles map[string]*Profile
profilesMu sync.Mutex
wgIface wgIface
wgMu sync.Mutex
}
func NewConnProfiler() *ConnProfiler {
return &ConnProfiler{
profiles: make(map[string]*Profile),
}
}
func (p *ConnProfiler) GetProfiles() map[string]Profile {
p.profilesMu.Lock()
defer p.profilesMu.Unlock()
copiedProfiles := make(map[string]Profile)
for key, profile := range p.profiles {
copiedProfiles[key] = Profile{
NetworkMapUpdate: profile.NetworkMapUpdate,
OfferSent: profile.OfferSent,
OfferReceived: profile.OfferReceived,
WireGuardConfigured: profile.WireGuardConfigured,
WireGuardConnected: profile.WireGuardConnected,
}
}
return copiedProfiles
}
func (p *ConnProfiler) WGInterfaceUP(wgInterface wgIface) {
p.wgMu.Lock()
defer p.wgMu.Unlock()
if p.wgIface != nil {
return
}
p.wgIface = wgInterface
go p.watchHandshakes()
}
func (p *ConnProfiler) NetworkMapUpdate(peerConfigs []*proto.RemotePeerConfig) {
p.profilesMu.Lock()
defer p.profilesMu.Unlock()
for _, peerConfig := range peerConfigs {
profile, ok := p.profiles[peerConfig.WgPubKey]
if ok {
continue
}
profile = &Profile{
NetworkMapUpdate: time.Now(),
}
p.profiles[peerConfig.WgPubKey] = profile
}
}
func (p *ConnProfiler) OfferSent(peerID string) {
p.profilesMu.Lock()
defer p.profilesMu.Unlock()
profile, ok := p.profiles[peerID]
if !ok {
log.Warnf("OfferSent: profile not found for peer %s", peerID)
return
}
if !profile.OfferSent.IsZero() {
return
}
profile.OfferSent = time.Now()
}
func (p *ConnProfiler) OfferAnswerReceived(peerID string) {
p.profilesMu.Lock()
defer p.profilesMu.Unlock()
profile, ok := p.profiles[peerID]
if !ok {
log.Warnf("OfferSent: profile not found for peer %s", peerID)
return
}
if !profile.OfferReceived.IsZero() {
return
}
profile.OfferReceived = time.Now()
}
func (p *ConnProfiler) WireGuardConfigured(peerID string) {
p.profilesMu.Lock()
defer p.profilesMu.Unlock()
profile, ok := p.profiles[peerID]
if !ok {
log.Warnf("OfferSent: profile not found for peer %s", peerID)
return
}
if !profile.WireGuardConfigured.IsZero() {
return
}
profile.WireGuardConfigured = time.Now()
}
func (p *ConnProfiler) watchHandshakes() {
ticker := time.NewTicker(300 * time.Millisecond)
for {
select {
case _ = <-ticker.C:
p.checkHandshakes()
}
}
}
func (p *ConnProfiler) checkHandshakes() {
stats, err := p.wgIface.GetAllStat()
if err != nil {
log.Errorf("watchHandshakes: %v", err)
return
}
p.profilesMu.Lock()
for peerID, profile := range p.profiles {
if !profile.WireGuardConnected.IsZero() {
continue
}
stat, ok := stats[peerID]
if !ok {
continue
}
if stat.LastHandshake.IsZero() {
continue
}
if stat.LastHandshake.Before(time.Now().Add(-100 * time.Hour)) {
continue
}
profile.WireGuardConnected = stat.LastHandshake
}
p.profilesMu.Unlock()
}

46
connprofile/report.go Normal file
View File

@@ -0,0 +1,46 @@
package connprofile
import (
"encoding/json"
"time"
log "github.com/sirupsen/logrus"
)
type Report struct {
NetworkMapUpdate time.Time
OfferSent float64
OfferReceived float64
WireGuardConfigured float64
WireGuardConnected float64
}
func report() {
ticker := time.NewTicker(5 * time.Second)
for {
select {
case _ = <-ticker.C:
printJson()
}
}
}
func printJson() {
profiles := Profiler.GetProfiles()
reports := make(map[string]Report)
for key, profile := range profiles {
reports[key] = Report{
NetworkMapUpdate: profile.NetworkMapUpdate,
OfferSent: profile.OfferSent.Sub(profile.NetworkMapUpdate).Seconds(),
OfferReceived: profile.OfferReceived.Sub(profile.OfferSent).Seconds(),
WireGuardConfigured: profile.WireGuardConfigured.Sub(profile.OfferReceived).Seconds(),
WireGuardConnected: profile.WireGuardConnected.Sub(profile.WireGuardConfigured).Seconds(),
}
}
jsonData, err := json.MarshalIndent(reports, "", " ")
if err != nil {
log.Errorf("failed to marshal profiles: %v", err)
}
log.Infof("profiles: %s", jsonData)
}

10
connprofile/static.go Normal file
View File

@@ -0,0 +1,10 @@
package connprofile
var (
Profiler *ConnProfiler
)
func init() {
Profiler = NewConnProfiler()
go report()
}

10
go.mod
View File

@@ -19,8 +19,8 @@ require (
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/vishvananda/netlink v1.2.1-beta.2
golang.org/x/crypto v0.28.0
golang.org/x/sys v0.26.0
golang.org/x/crypto v0.31.0
golang.org/x/sys v0.28.0
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
golang.zx2c4.com/wireguard/windows v0.5.3
@@ -92,8 +92,8 @@ require (
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a
golang.org/x/net v0.30.0
golang.org/x/oauth2 v0.19.0
golang.org/x/sync v0.8.0
golang.org/x/term v0.25.0
golang.org/x/sync v0.10.0
golang.org/x/term v0.27.0
google.golang.org/api v0.177.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.7
@@ -219,7 +219,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect

20
go.sum
View File

@@ -774,8 +774,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -901,8 +901,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -974,8 +974,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -983,8 +983,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -999,8 +999,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -530,7 +530,7 @@ renderCaddyfile() {
{
debug
servers :80,:443 {
protocols h1 h2c h3
protocols h1 h2c h2 h3
}
}

View File

@@ -239,7 +239,12 @@ install_netbird() {
dnf)
add_rpm_repo
${SUDO} dnf -y install dnf-plugin-config-manager
${SUDO} dnf config-manager --add-repo /etc/yum.repos.d/netbird.repo
if [[ "$(dnf --version | head -n1 | cut -d. -f1)" > "4" ]];
then
${SUDO} dnf config-manager addrepo --from-repofile=/etc/yum.repos.d/netbird.repo
else
${SUDO} dnf config-manager --add-repo /etc/yum.repos.d/netbird.repo
fi
${SUDO} dnf -y install netbird
if ! $SKIP_UI_APP; then