diff --git a/.github/ISSUE_TEMPLATE/bug-issue-report.md b/.github/ISSUE_TEMPLATE/bug-issue-report.md
index 87f757f42..3633cca4f 100644
--- a/.github/ISSUE_TEMPLATE/bug-issue-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-issue-report.md
@@ -31,14 +31,22 @@ Please specify whether you use NetBird Cloud or self-host NetBird's control plan
`netbird version`
-**NetBird status -dA output:**
+**Is any other VPN software installed?**
-If applicable, add the `netbird status -dA' command output.
+If yes, which one?
-**Do you face any (non-mobile) client issues?**
+**Debug output**
-Please provide the file created by `netbird debug for 1m -AS`.
-We advise reviewing the anonymized files for any remaining PII.
+To help us resolve the problem, please attach the following debug output
+
+ netbird status -dA
+
+As well as the file created by
+
+ netbird debug for 1m -AS
+
+
+We advise reviewing the anonymized output for any remaining personal information.
**Screenshots**
@@ -47,3 +55,10 @@ If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+
+**Have you tried these troubleshooting steps?**
+- [ ] Checked for newer NetBird versions
+- [ ] Searched for similar issues on GitHub (including closed ones)
+- [ ] Restarted the NetBird client
+- [ ] Disabled other VPN software
+- [ ] Checked firewall settings
diff --git a/.github/workflows/golang-test-linux.yml b/.github/workflows/golang-test-linux.yml
index 3be8bcff3..cf061f876 100644
--- a/.github/workflows/golang-test-linux.yml
+++ b/.github/workflows/golang-test-linux.yml
@@ -258,7 +258,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- arch: [ '386','amd64' ]
+ arch: [ 'amd64' ]
store: [ 'sqlite', 'postgres', 'mysql' ]
runs-on: ubuntu-22.04
steps:
@@ -325,8 +325,8 @@ jobs:
strategy:
fail-fast: false
matrix:
- arch: [ '386','amd64' ]
- store: [ 'sqlite', 'postgres', 'mysql' ]
+ arch: [ 'amd64' ]
+ store: [ 'sqlite', 'postgres' ]
runs-on: ubuntu-22.04
steps:
- name: Install Go
@@ -392,7 +392,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- arch: [ '386','amd64' ]
+ arch: [ 'amd64' ]
store: [ 'sqlite', 'postgres' ]
runs-on: ubuntu-22.04
steps:
@@ -461,7 +461,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- arch: [ '386','amd64' ]
+ arch: [ 'amd64' ]
store: [ 'sqlite', 'postgres']
runs-on: ubuntu-22.04
steps:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 04874bdf4..919351f18 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -71,7 +71,7 @@ jobs:
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- name: Generate windows syso amd64
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
+ run: goversioninfo -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
@@ -150,7 +150,7 @@ jobs:
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- name: Generate windows syso amd64
- run: goversioninfo -64 -icon client/ui/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
+ run: goversioninfo -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
diff --git a/.goreleaser_ui.yaml b/.goreleaser_ui.yaml
index 1dd649d1b..459f204d3 100644
--- a/.goreleaser_ui.yaml
+++ b/.goreleaser_ui.yaml
@@ -53,9 +53,9 @@ nfpms:
scripts:
postinstall: "release_files/ui-post-install.sh"
contents:
- - src: client/ui/netbird.desktop
+ - src: client/ui/build/netbird.desktop
dst: /usr/share/applications/netbird.desktop
- - src: client/ui/netbird.png
+ - src: client/ui/assets/netbird.png
dst: /usr/share/pixmaps/netbird.png
dependencies:
- netbird
@@ -72,9 +72,9 @@ nfpms:
scripts:
postinstall: "release_files/ui-post-install.sh"
contents:
- - src: client/ui/netbird.desktop
+ - src: client/ui/build/netbird.desktop
dst: /usr/share/applications/netbird.desktop
- - src: client/ui/netbird.png
+ - src: client/ui/assets/netbird.png
dst: /usr/share/pixmaps/netbird.png
dependencies:
- netbird
diff --git a/README.md b/README.md
index 5b136eff6..a063c821d 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
@@ -29,7 +29,7 @@
See Documentation
- Join our Slack channel
+ Join our Slack channel
diff --git a/client/embed/embed.go b/client/embed/embed.go
index 9ded618c5..fe95b1942 100644
--- a/client/embed/embed.go
+++ b/client/embed/embed.go
@@ -134,10 +134,11 @@ func (c *Client) Start(startCtx context.Context) error {
// either startup error (permanent backoff err) or nil err (successful engine up)
// TODO: make after-startup backoff err available
- run := make(chan error, 1)
+ run := make(chan struct{}, 1)
+ clientErr := make(chan error, 1)
go func() {
if err := client.Run(run); err != nil {
- run <- err
+ clientErr <- err
}
}()
@@ -147,13 +148,9 @@ func (c *Client) Start(startCtx context.Context) error {
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)
- }
+ case err := <-clientErr:
+ return fmt.Errorf("startup: %w", err)
+ case <-run:
}
c.connect = client
diff --git a/client/firewall/iface.go b/client/firewall/iface.go
index d842abaa1..b83c5f912 100644
--- a/client/firewall/iface.go
+++ b/client/firewall/iface.go
@@ -4,12 +4,13 @@ import (
wgdevice "golang.zx2c4.com/wireguard/device"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// IFaceMapper defines subset methods of interface required for manager
type IFaceMapper interface {
Name() string
- Address() device.WGAddress
+ Address() wgaddr.Address
IsUserspaceBind() bool
SetFilter(device.PacketFilter) error
GetDevice() *device.FilteredDevice
diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go
index f8c8945e6..652ab1b3e 100644
--- a/client/firewall/iptables/manager_linux.go
+++ b/client/firewall/iptables/manager_linux.go
@@ -13,7 +13,7 @@ import (
nberrors "github.com/netbirdio/netbird/client/errors"
firewall "github.com/netbirdio/netbird/client/firewall/manager"
- "github.com/netbirdio/netbird/client/iface"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/statemanager"
)
@@ -31,7 +31,7 @@ type Manager struct {
// iFaceMapper defines subset methods of interface required for manager
type iFaceMapper interface {
Name() string
- Address() iface.WGAddress
+ Address() wgaddr.Address
IsUserspaceBind() bool
}
@@ -167,7 +167,7 @@ func (m *Manager) SetLegacyManagement(isLegacy bool) error {
}
// Reset firewall to the default state
-func (m *Manager) Reset(stateManager *statemanager.Manager) error {
+func (m *Manager) Close(stateManager *statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
diff --git a/client/firewall/iptables/manager_linux_test.go b/client/firewall/iptables/manager_linux_test.go
index 0dcbc8f5d..af9f5dd23 100644
--- a/client/firewall/iptables/manager_linux_test.go
+++ b/client/firewall/iptables/manager_linux_test.go
@@ -10,15 +10,15 @@ import (
"github.com/stretchr/testify/require"
fw "github.com/netbirdio/netbird/client/firewall/manager"
- "github.com/netbirdio/netbird/client/iface"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
var ifaceMock = &iFaceMock{
NameFunc: func() string {
return "lo"
},
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("10.20.0.1"),
Network: &net.IPNet{
IP: net.ParseIP("10.20.0.0"),
@@ -31,7 +31,7 @@ var ifaceMock = &iFaceMock{
// iFaceMapper defines subset methods of interface required for manager
type iFaceMock struct {
NameFunc func() string
- AddressFunc func() iface.WGAddress
+ AddressFunc func() wgaddr.Address
}
func (i *iFaceMock) Name() string {
@@ -41,7 +41,7 @@ func (i *iFaceMock) Name() string {
panic("NameFunc is not set")
}
-func (i *iFaceMock) Address() iface.WGAddress {
+func (i *iFaceMock) Address() wgaddr.Address {
if i.AddressFunc != nil {
return i.AddressFunc()
}
@@ -62,7 +62,7 @@ func TestIptablesManager(t *testing.T) {
time.Sleep(time.Second)
defer func() {
- err := manager.Reset(nil)
+ err := manager.Close(nil)
require.NoError(t, err, "clear the manager state")
time.Sleep(time.Second)
@@ -100,14 +100,14 @@ func TestIptablesManager(t *testing.T) {
_, err = manager.AddPeerFiltering(nil, ip, "udp", nil, port, fw.ActionAccept, "")
require.NoError(t, err, "failed to add rule")
- err = manager.Reset(nil)
+ err = manager.Close(nil)
require.NoError(t, err, "failed to reset")
ok, err := ipv4Client.ChainExists("filter", chainNameInputRules)
require.NoError(t, err, "failed check chain exists")
if ok {
- require.NoErrorf(t, err, "chain '%v' still exists after Reset", chainNameInputRules)
+ require.NoErrorf(t, err, "chain '%v' still exists after Close", chainNameInputRules)
}
})
}
@@ -117,8 +117,8 @@ func TestIptablesManagerIPSet(t *testing.T) {
NameFunc: func() string {
return "lo"
},
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("10.20.0.1"),
Network: &net.IPNet{
IP: net.ParseIP("10.20.0.0"),
@@ -136,7 +136,7 @@ func TestIptablesManagerIPSet(t *testing.T) {
time.Sleep(time.Second)
defer func() {
- err := manager.Reset(nil)
+ err := manager.Close(nil)
require.NoError(t, err, "clear the manager state")
time.Sleep(time.Second)
@@ -166,7 +166,7 @@ func TestIptablesManagerIPSet(t *testing.T) {
})
t.Run("reset check", func(t *testing.T) {
- err = manager.Reset(nil)
+ err = manager.Close(nil)
require.NoError(t, err, "failed to reset")
})
}
@@ -184,8 +184,8 @@ func TestIptablesCreatePerformance(t *testing.T) {
NameFunc: func() string {
return "lo"
},
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("10.20.0.1"),
Network: &net.IPNet{
IP: net.ParseIP("10.20.0.0"),
@@ -204,7 +204,7 @@ func TestIptablesCreatePerformance(t *testing.T) {
time.Sleep(time.Second)
defer func() {
- err := manager.Reset(nil)
+ err := manager.Close(nil)
require.NoError(t, err, "clear the manager state")
time.Sleep(time.Second)
diff --git a/client/firewall/iptables/state_linux.go b/client/firewall/iptables/state_linux.go
index 44b8340ba..6ef159e01 100644
--- a/client/firewall/iptables/state_linux.go
+++ b/client/firewall/iptables/state_linux.go
@@ -4,21 +4,20 @@ import (
"fmt"
"sync"
- "github.com/netbirdio/netbird/client/iface"
- "github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type InterfaceState struct {
- NameStr string `json:"name"`
- WGAddress iface.WGAddress `json:"wg_address"`
- UserspaceBind bool `json:"userspace_bind"`
+ NameStr string `json:"name"`
+ WGAddress wgaddr.Address `json:"wg_address"`
+ UserspaceBind bool `json:"userspace_bind"`
}
func (i *InterfaceState) Name() string {
return i.NameStr
}
-func (i *InterfaceState) Address() device.WGAddress {
+func (i *InterfaceState) Address() wgaddr.Address {
return i.WGAddress
}
@@ -62,7 +61,7 @@ func (s *ShutdownState) Cleanup() error {
ipt.aclMgr.ipsetStore = s.ACLIPsetStore
}
- if err := ipt.Reset(nil); err != nil {
+ if err := ipt.Close(nil); err != nil {
return fmt.Errorf("reset iptables manager: %w", err)
}
diff --git a/client/firewall/manager/firewall.go b/client/firewall/manager/firewall.go
index 6d369f649..1d71051ef 100644
--- a/client/firewall/manager/firewall.go
+++ b/client/firewall/manager/firewall.go
@@ -102,8 +102,8 @@ type Manager interface {
// SetLegacyManagement sets the legacy management mode
SetLegacyManagement(legacy bool) error
- // Reset firewall to the default state
- Reset(stateManager *statemanager.Manager) error
+ // Close closes the firewall manager
+ Close(stateManager *statemanager.Manager) error
// Flush the changes to firewall controller
Flush() error
diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go
index 878ede2da..a5809471c 100644
--- a/client/firewall/nftables/manager_linux.go
+++ b/client/firewall/nftables/manager_linux.go
@@ -14,7 +14,7 @@ import (
log "github.com/sirupsen/logrus"
firewall "github.com/netbirdio/netbird/client/firewall/manager"
- "github.com/netbirdio/netbird/client/iface"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/statemanager"
)
@@ -29,7 +29,7 @@ const (
// iFaceMapper defines subset methods of interface required for manager
type iFaceMapper interface {
Name() string
- Address() iface.WGAddress
+ Address() wgaddr.Address
IsUserspaceBind() bool
}
@@ -87,7 +87,7 @@ func (m *Manager) Init(stateManager *statemanager.Manager) error {
// We only need to record minimal interface state for potential recreation.
// Unlike iptables, which requires tracking individual rules, nftables maintains
// a known state (our netbird table plus a few static rules). This allows for easy
- // cleanup using Reset() without needing to store specific rules.
+ // cleanup using Close() without needing to store specific rules.
if err := stateManager.UpdateState(&ShutdownState{
InterfaceState: &InterfaceState{
NameStr: m.wgIface.Name(),
@@ -243,7 +243,7 @@ func (m *Manager) SetLegacyManagement(isLegacy bool) error {
}
// Reset firewall to the default state
-func (m *Manager) Reset(stateManager *statemanager.Manager) error {
+func (m *Manager) Close(stateManager *statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go
index bfdffd9e3..373743a08 100644
--- a/client/firewall/nftables/manager_linux_test.go
+++ b/client/firewall/nftables/manager_linux_test.go
@@ -16,15 +16,15 @@ import (
"golang.org/x/sys/unix"
fw "github.com/netbirdio/netbird/client/firewall/manager"
- "github.com/netbirdio/netbird/client/iface"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
var ifaceMock = &iFaceMock{
NameFunc: func() string {
return "lo"
},
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("100.96.0.1"),
Network: &net.IPNet{
IP: net.ParseIP("100.96.0.0"),
@@ -37,7 +37,7 @@ var ifaceMock = &iFaceMock{
// iFaceMapper defines subset methods of interface required for manager
type iFaceMock struct {
NameFunc func() string
- AddressFunc func() iface.WGAddress
+ AddressFunc func() wgaddr.Address
}
func (i *iFaceMock) Name() string {
@@ -47,7 +47,7 @@ func (i *iFaceMock) Name() string {
panic("NameFunc is not set")
}
-func (i *iFaceMock) Address() iface.WGAddress {
+func (i *iFaceMock) Address() wgaddr.Address {
if i.AddressFunc != nil {
return i.AddressFunc()
}
@@ -65,7 +65,7 @@ func TestNftablesManager(t *testing.T) {
time.Sleep(time.Second * 3)
defer func() {
- err = manager.Reset(nil)
+ err = manager.Close(nil)
require.NoError(t, err, "failed to reset")
time.Sleep(time.Second)
}()
@@ -162,7 +162,7 @@ func TestNftablesManager(t *testing.T) {
// established rule remains
require.Len(t, rules, 1, "expected 1 rules after deletion")
- err = manager.Reset(nil)
+ err = manager.Close(nil)
require.NoError(t, err, "failed to reset")
}
@@ -171,8 +171,8 @@ func TestNFtablesCreatePerformance(t *testing.T) {
NameFunc: func() string {
return "lo"
},
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("100.96.0.1"),
Network: &net.IPNet{
IP: net.ParseIP("100.96.0.0"),
@@ -191,7 +191,7 @@ func TestNFtablesCreatePerformance(t *testing.T) {
time.Sleep(time.Second * 3)
defer func() {
- if err := manager.Reset(nil); err != nil {
+ if err := manager.Close(nil); err != nil {
t.Errorf("clear the manager state: %v", err)
}
time.Sleep(time.Second)
@@ -274,7 +274,7 @@ func TestNftablesManagerCompatibilityWithIptables(t *testing.T) {
require.NoError(t, manager.Init(nil))
t.Cleanup(func() {
- err := manager.Reset(nil)
+ err := manager.Close(nil)
require.NoError(t, err, "failed to reset manager state")
// Verify iptables output after reset
diff --git a/client/firewall/nftables/router_linux_test.go b/client/firewall/nftables/router_linux_test.go
index 87a219a70..498fdf882 100644
--- a/client/firewall/nftables/router_linux_test.go
+++ b/client/firewall/nftables/router_linux_test.go
@@ -38,7 +38,7 @@ func TestNftablesManager_AddNatRule(t *testing.T) {
// need fw manager to init both acl mgr and router for all chains to be present
manager, err := Create(ifaceMock)
t.Cleanup(func() {
- require.NoError(t, manager.Reset(nil))
+ require.NoError(t, manager.Close(nil))
})
require.NoError(t, err)
require.NoError(t, manager.Init(nil))
@@ -127,7 +127,7 @@ func TestNftablesManager_RemoveNatRule(t *testing.T) {
t.Run(testCase.Name, func(t *testing.T) {
manager, err := Create(ifaceMock)
t.Cleanup(func() {
- require.NoError(t, manager.Reset(nil))
+ require.NoError(t, manager.Close(nil))
})
require.NoError(t, err)
require.NoError(t, manager.Init(nil))
diff --git a/client/firewall/nftables/state_linux.go b/client/firewall/nftables/state_linux.go
index a68c8b8b8..f805623d6 100644
--- a/client/firewall/nftables/state_linux.go
+++ b/client/firewall/nftables/state_linux.go
@@ -3,21 +3,20 @@ package nftables
import (
"fmt"
- "github.com/netbirdio/netbird/client/iface"
- "github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type InterfaceState struct {
- NameStr string `json:"name"`
- WGAddress iface.WGAddress `json:"wg_address"`
- UserspaceBind bool `json:"userspace_bind"`
+ NameStr string `json:"name"`
+ WGAddress wgaddr.Address `json:"wg_address"`
+ UserspaceBind bool `json:"userspace_bind"`
}
func (i *InterfaceState) Name() string {
return i.NameStr
}
-func (i *InterfaceState) Address() device.WGAddress {
+func (i *InterfaceState) Address() wgaddr.Address {
return i.WGAddress
}
@@ -39,7 +38,7 @@ func (s *ShutdownState) Cleanup() error {
return fmt.Errorf("create nftables manager: %w", err)
}
- if err := nft.Reset(nil); err != nil {
+ if err := nft.Close(nil); err != nil {
return fmt.Errorf("reset nftables manager: %w", err)
}
diff --git a/client/firewall/uspfilter/allow_netbird.go b/client/firewall/uspfilter/allow_netbird.go
index 1982e4e7e..5fe698aa9 100644
--- a/client/firewall/uspfilter/allow_netbird.go
+++ b/client/firewall/uspfilter/allow_netbird.go
@@ -4,39 +4,36 @@ package uspfilter
import (
"context"
+ "net/netip"
"time"
log "github.com/sirupsen/logrus"
- "github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
"github.com/netbirdio/netbird/client/internal/statemanager"
)
// Reset firewall to the default state
-func (m *Manager) Reset(stateManager *statemanager.Manager) error {
+func (m *Manager) Close(stateManager *statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
- m.outgoingRules = make(map[string]RuleSet)
- m.incomingRules = make(map[string]RuleSet)
+ m.outgoingRules = make(map[netip.Addr]RuleSet)
+ m.incomingRules = make(map[netip.Addr]RuleSet)
if m.udpTracker != nil {
m.udpTracker.Close()
- m.udpTracker = conntrack.NewUDPTracker(conntrack.DefaultUDPTimeout, m.logger, m.flowLogger)
}
if m.icmpTracker != nil {
m.icmpTracker.Close()
- m.icmpTracker = conntrack.NewICMPTracker(conntrack.DefaultICMPTimeout, m.logger, m.flowLogger)
}
if m.tcpTracker != nil {
m.tcpTracker.Close()
- m.tcpTracker = conntrack.NewTCPTracker(conntrack.DefaultTCPTimeout, m.logger, m.flowLogger)
}
- if m.forwarder != nil {
- m.forwarder.Stop()
+ if fwder := m.forwarder.Load(); fwder != nil {
+ fwder.Stop()
}
if m.logger != nil {
@@ -48,7 +45,7 @@ func (m *Manager) Reset(stateManager *statemanager.Manager) error {
}
if m.nativeFirewall != nil {
- return m.nativeFirewall.Reset(stateManager)
+ return m.nativeFirewall.Close(stateManager)
}
return nil
}
diff --git a/client/firewall/uspfilter/allow_netbird_windows.go b/client/firewall/uspfilter/allow_netbird_windows.go
index cacabe1b3..f63792fec 100644
--- a/client/firewall/uspfilter/allow_netbird_windows.go
+++ b/client/firewall/uspfilter/allow_netbird_windows.go
@@ -3,6 +3,7 @@ package uspfilter
import (
"context"
"fmt"
+ "net/netip"
"os/exec"
"syscall"
"time"
@@ -22,12 +23,12 @@ const (
)
// Reset firewall to the default state
-func (m *Manager) Reset(*statemanager.Manager) error {
+func (m *Manager) Close(*statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
- m.outgoingRules = make(map[string]RuleSet)
- m.incomingRules = make(map[string]RuleSet)
+ m.outgoingRules = make(map[netip.Addr]RuleSet)
+ m.incomingRules = make(map[netip.Addr]RuleSet)
if m.udpTracker != nil {
m.udpTracker.Close()
@@ -44,8 +45,8 @@ func (m *Manager) Reset(*statemanager.Manager) error {
m.tcpTracker = conntrack.NewTCPTracker(conntrack.DefaultTCPTimeout, m.logger, m.flowLogger)
}
- if m.forwarder != nil {
- m.forwarder.Stop()
+ if fwder := m.forwarder.Load(); fwder != nil {
+ fwder.Stop()
}
if m.logger != nil {
diff --git a/client/firewall/uspfilter/common/iface.go b/client/firewall/uspfilter/common/iface.go
index d44e79509..7296953db 100644
--- a/client/firewall/uspfilter/common/iface.go
+++ b/client/firewall/uspfilter/common/iface.go
@@ -3,14 +3,14 @@ package common
import (
wgdevice "golang.zx2c4.com/wireguard/device"
- "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// IFaceMapper defines subset methods of interface required for manager
type IFaceMapper interface {
SetFilter(device.PacketFilter) error
- Address() iface.WGAddress
+ Address() wgaddr.Address
GetWGDevice() *wgdevice.Device
GetDevice() *device.FilteredDevice
}
diff --git a/client/firewall/uspfilter/conntrack/common.go b/client/firewall/uspfilter/conntrack/common.go
index 915b57549..3de0bb3f4 100644
--- a/client/firewall/uspfilter/conntrack/common.go
+++ b/client/firewall/uspfilter/conntrack/common.go
@@ -2,7 +2,6 @@ package conntrack
import (
"fmt"
- "net"
"net/netip"
"sync/atomic"
"time"
@@ -14,13 +13,15 @@ import (
// BaseConnTrack provides common fields and locking for all connection types
type BaseConnTrack struct {
- FlowId uuid.UUID
- Direction nftypes.Direction
- SourceIP netip.Addr
- DestIP netip.Addr
- SourcePort uint16
- DestPort uint16
- lastSeen atomic.Int64
+ FlowId uuid.UUID
+ Direction nftypes.Direction
+ SourceIP netip.Addr
+ DestIP netip.Addr
+ lastSeen atomic.Int64
+ PacketsTx atomic.Uint64
+ PacketsRx atomic.Uint64
+ BytesTx atomic.Uint64
+ BytesRx atomic.Uint64
}
// these small methods will be inlined by the compiler
@@ -30,6 +31,17 @@ func (b *BaseConnTrack) UpdateLastSeen() {
b.lastSeen.Store(time.Now().UnixNano())
}
+// UpdateCounters safely updates the packet and byte counters
+func (b *BaseConnTrack) UpdateCounters(direction nftypes.Direction, bytes int) {
+ if direction == nftypes.Egress {
+ b.PacketsTx.Add(1)
+ b.BytesTx.Add(uint64(bytes))
+ } else {
+ b.PacketsRx.Add(1)
+ b.BytesRx.Add(uint64(bytes))
+ }
+}
+
// GetLastSeen safely gets the last seen timestamp
func (b *BaseConnTrack) GetLastSeen() time.Time {
return time.Unix(0, b.lastSeen.Load())
@@ -52,16 +64,3 @@ type ConnKey struct {
func (c ConnKey) String() string {
return fmt.Sprintf("%s:%d -> %s:%d", c.SrcIP.Unmap(), c.SrcPort, c.DstIP.Unmap(), c.DstPort)
}
-
-// makeConnKey creates a connection key
-func makeConnKey(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16) ConnKey {
- srcAddr, _ := netip.AddrFromSlice(srcIP)
- dstAddr, _ := netip.AddrFromSlice(dstIP)
-
- return ConnKey{
- SrcIP: srcAddr,
- DstIP: dstAddr,
- SrcPort: srcPort,
- DstPort: dstPort,
- }
-}
diff --git a/client/firewall/uspfilter/conntrack/common_test.go b/client/firewall/uspfilter/conntrack/common_test.go
index 6d1ed5890..f28cd56e5 100644
--- a/client/firewall/uspfilter/conntrack/common_test.go
+++ b/client/firewall/uspfilter/conntrack/common_test.go
@@ -2,7 +2,7 @@ package conntrack
import (
"context"
- "net"
+ "net/netip"
"testing"
"github.com/sirupsen/logrus"
@@ -12,7 +12,7 @@ import (
)
var logger = log.NewFromLogrus(logrus.StandardLogger())
-var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}).GetLogger()
+var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}, nil).GetLogger()
// Memory pressure tests
func BenchmarkMemoryPressure(b *testing.B) {
@@ -21,22 +21,22 @@ func BenchmarkMemoryPressure(b *testing.B) {
defer tracker.Close()
// Generate different IPs
- srcIPs := make([]net.IP, 100)
- dstIPs := make([]net.IP, 100)
+ srcIPs := make([]netip.Addr, 100)
+ dstIPs := make([]netip.Addr, 100)
for i := 0; i < 100; i++ {
- srcIPs[i] = net.IPv4(192, 168, byte(i/256), byte(i%256))
- dstIPs[i] = net.IPv4(10, 0, byte(i/256), byte(i%256))
+ srcIPs[i] = netip.AddrFrom4([4]byte{192, 168, byte(i / 256), byte(i % 256)})
+ dstIPs[i] = netip.AddrFrom4([4]byte{10, 0, byte(i / 256), byte(i % 256)})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
srcIdx := i % len(srcIPs)
dstIdx := (i + 1) % len(dstIPs)
- tracker.TrackOutbound(srcIPs[srcIdx], dstIPs[dstIdx], uint16(i%65535), 80, TCPSyn)
+ tracker.TrackOutbound(srcIPs[srcIdx], dstIPs[dstIdx], uint16(i%65535), 80, TCPSyn, 0)
// Simulate some valid inbound packets
if i%3 == 0 {
- tracker.IsValidInbound(dstIPs[dstIdx], srcIPs[srcIdx], 80, uint16(i%65535), TCPAck)
+ tracker.IsValidInbound(dstIPs[dstIdx], srcIPs[srcIdx], 80, uint16(i%65535), TCPAck, 0)
}
}
})
@@ -46,22 +46,22 @@ func BenchmarkMemoryPressure(b *testing.B) {
defer tracker.Close()
// Generate different IPs
- srcIPs := make([]net.IP, 100)
- dstIPs := make([]net.IP, 100)
+ srcIPs := make([]netip.Addr, 100)
+ dstIPs := make([]netip.Addr, 100)
for i := 0; i < 100; i++ {
- srcIPs[i] = net.IPv4(192, 168, byte(i/256), byte(i%256))
- dstIPs[i] = net.IPv4(10, 0, byte(i/256), byte(i%256))
+ srcIPs[i] = netip.AddrFrom4([4]byte{192, 168, byte(i / 256), byte(i % 256)})
+ dstIPs[i] = netip.AddrFrom4([4]byte{10, 0, byte(i / 256), byte(i % 256)})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
srcIdx := i % len(srcIPs)
dstIdx := (i + 1) % len(dstIPs)
- tracker.TrackOutbound(srcIPs[srcIdx], dstIPs[dstIdx], uint16(i%65535), 80)
+ tracker.TrackOutbound(srcIPs[srcIdx], dstIPs[dstIdx], uint16(i%65535), 80, 0)
// Simulate some valid inbound packets
if i%3 == 0 {
- tracker.IsValidInbound(dstIPs[dstIdx], srcIPs[srcIdx], 80, uint16(i%65535))
+ tracker.IsValidInbound(dstIPs[dstIdx], srcIPs[srcIdx], 80, uint16(i%65535), 0)
}
}
})
diff --git a/client/firewall/uspfilter/conntrack/icmp.go b/client/firewall/uspfilter/conntrack/icmp.go
index fda08154c..1a6566fa8 100644
--- a/client/firewall/uspfilter/conntrack/icmp.go
+++ b/client/firewall/uspfilter/conntrack/icmp.go
@@ -1,8 +1,8 @@
package conntrack
import (
+ "context"
"fmt"
- "net"
"net/netip"
"sync"
"time"
@@ -23,14 +23,13 @@ const (
// ICMPConnKey uniquely identifies an ICMP connection
type ICMPConnKey struct {
- SrcIP netip.Addr
- DstIP netip.Addr
- Sequence uint16
- ID uint16
+ SrcIP netip.Addr
+ DstIP netip.Addr
+ ID uint16
}
func (i ICMPConnKey) String() string {
- return fmt.Sprintf("%s -> %s (%d/%d)", i.SrcIP, i.DstIP, i.ID, i.Sequence)
+ return fmt.Sprintf("%s -> %s (id %d)", i.SrcIP, i.DstIP, i.ID)
}
// ICMPConnTrack represents an ICMP connection state
@@ -46,8 +45,8 @@ type ICMPTracker struct {
connections map[ICMPConnKey]*ICMPConnTrack
timeout time.Duration
cleanupTicker *time.Ticker
+ tickerCancel context.CancelFunc
mutex sync.RWMutex
- done chan struct{}
flowLogger nftypes.FlowLogger
}
@@ -57,21 +56,27 @@ func NewICMPTracker(timeout time.Duration, logger *nblog.Logger, flowLogger nfty
timeout = DefaultICMPTimeout
}
+ ctx, cancel := context.WithCancel(context.Background())
+
tracker := &ICMPTracker{
logger: logger,
connections: make(map[ICMPConnKey]*ICMPConnTrack),
timeout: timeout,
cleanupTicker: time.NewTicker(ICMPCleanupInterval),
- done: make(chan struct{}),
+ tickerCancel: cancel,
flowLogger: flowLogger,
}
- go tracker.cleanupRoutine()
+ go tracker.cleanupRoutine(ctx)
return tracker
}
-func (t *ICMPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, id uint16, seq uint16) (ICMPConnKey, bool) {
- key := makeICMPKey(srcIP, dstIP, id, seq)
+func (t *ICMPTracker) updateIfExists(srcIP netip.Addr, dstIP netip.Addr, id uint16, direction nftypes.Direction, size int) (ICMPConnKey, bool) {
+ key := ICMPConnKey{
+ SrcIP: srcIP,
+ DstIP: dstIP,
+ ID: id,
+ }
t.mutex.RLock()
conn, exists := t.connections[key]
@@ -79,6 +84,7 @@ func (t *ICMPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, id uint16, seq
if exists {
conn.UpdateLastSeen()
+ conn.UpdateCounters(direction, size)
return key, true
}
@@ -87,22 +93,21 @@ func (t *ICMPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, id uint16, seq
}
// TrackOutbound records an outbound ICMP connection
-func (t *ICMPTracker) TrackOutbound(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, typecode layers.ICMPv4TypeCode) {
- if _, exists := t.updateIfExists(dstIP, srcIP, id, seq); !exists {
+func (t *ICMPTracker) TrackOutbound(srcIP netip.Addr, dstIP netip.Addr, id uint16, typecode layers.ICMPv4TypeCode, size int) {
+ if _, exists := t.updateIfExists(dstIP, srcIP, id, nftypes.Egress, size); !exists {
// if (inverted direction) conn is not tracked, track this direction
- t.track(srcIP, dstIP, id, seq, typecode, nftypes.Egress)
+ t.track(srcIP, dstIP, id, typecode, nftypes.Egress, nil, size)
}
}
// TrackInbound records an inbound ICMP Echo Request
-func (t *ICMPTracker) TrackInbound(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, typecode layers.ICMPv4TypeCode) {
- t.track(srcIP, dstIP, id, seq, typecode, nftypes.Ingress)
+func (t *ICMPTracker) TrackInbound(srcIP netip.Addr, dstIP netip.Addr, id uint16, typecode layers.ICMPv4TypeCode, ruleId []byte, size int) {
+ t.track(srcIP, dstIP, id, typecode, nftypes.Ingress, ruleId, size)
}
// track is the common implementation for tracking both inbound and outbound ICMP connections
-func (t *ICMPTracker) track(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, typecode layers.ICMPv4TypeCode, direction nftypes.Direction) {
- // TODO: icmp doesn't need to extend the timeout
- key, exists := t.updateIfExists(srcIP, dstIP, id, seq)
+func (t *ICMPTracker) track(srcIP netip.Addr, dstIP netip.Addr, id uint16, typecode layers.ICMPv4TypeCode, direction nftypes.Direction, ruleId []byte, size int) {
+ key, exists := t.updateIfExists(srcIP, dstIP, id, direction, size)
if exists {
return
}
@@ -112,7 +117,7 @@ func (t *ICMPTracker) track(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, t
// non echo requests don't need tracking
if typ != uint8(layers.ICMPv4TypeEchoRequest) {
t.logger.Trace("New %s ICMP connection %s type %d code %d", direction, key, typ, code)
- t.sendStartEvent(direction, key, typ, code)
+ t.sendStartEvent(direction, srcIP, dstIP, typ, code, ruleId, size)
return
}
@@ -120,8 +125,8 @@ func (t *ICMPTracker) track(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, t
BaseConnTrack: BaseConnTrack{
FlowId: uuid.New(),
Direction: direction,
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
+ SourceIP: srcIP,
+ DestIP: dstIP,
},
ICMPType: typ,
ICMPCode: code,
@@ -133,16 +138,20 @@ func (t *ICMPTracker) track(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, t
t.mutex.Unlock()
t.logger.Trace("New %s ICMP connection %s type %d code %d", direction, key, typ, code)
- t.sendEvent(nftypes.TypeStart, key, conn)
+ t.sendEvent(nftypes.TypeStart, conn, ruleId)
}
// IsValidInbound checks if an inbound ICMP Echo Reply matches a tracked request
-func (t *ICMPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, id uint16, seq uint16, icmpType uint8) bool {
+func (t *ICMPTracker) IsValidInbound(srcIP netip.Addr, dstIP netip.Addr, id uint16, icmpType uint8, size int) bool {
if icmpType != uint8(layers.ICMPv4TypeEchoReply) {
return false
}
- key := makeICMPKey(dstIP, srcIP, id, seq)
+ key := ICMPConnKey{
+ SrcIP: dstIP,
+ DstIP: srcIP,
+ ID: id,
+ }
t.mutex.RLock()
conn, exists := t.connections[key]
@@ -153,16 +162,19 @@ func (t *ICMPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, id uint16, seq
}
conn.UpdateLastSeen()
+ conn.UpdateCounters(nftypes.Ingress, size)
return true
}
-func (t *ICMPTracker) cleanupRoutine() {
+func (t *ICMPTracker) cleanupRoutine(ctx context.Context) {
+ defer t.tickerCancel()
+
for {
select {
case <-t.cleanupTicker.C:
t.cleanup()
- case <-t.done:
+ case <-ctx.Done():
return
}
}
@@ -176,56 +188,58 @@ func (t *ICMPTracker) cleanup() {
if conn.timeoutExceeded(t.timeout) {
delete(t.connections, key)
- t.logger.Debug("Removed ICMP connection %s (timeout)", &key)
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.logger.Debug("Removed ICMP connection %s (timeout) [in: %d Pkts/%d B out: %d Pkts/%d B]",
+ key, conn.PacketsRx.Load(), conn.BytesRx.Load(), conn.PacketsTx.Load(), conn.BytesTx.Load())
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
}
}
// Close stops the cleanup routine and releases resources
func (t *ICMPTracker) Close() {
- t.cleanupTicker.Stop()
- close(t.done)
+ t.tickerCancel()
t.mutex.Lock()
t.connections = nil
t.mutex.Unlock()
}
-func (t *ICMPTracker) sendEvent(typ nftypes.Type, key ICMPConnKey, conn *ICMPConnTrack) {
+func (t *ICMPTracker) sendEvent(typ nftypes.Type, conn *ICMPConnTrack, ruleID []byte) {
t.flowLogger.StoreEvent(nftypes.EventFields{
FlowID: conn.FlowId,
Type: typ,
+ RuleID: ruleID,
Direction: conn.Direction,
Protocol: nftypes.ICMP, // TODO: adjust for IPv6/icmpv6
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
+ SourceIP: conn.SourceIP,
+ DestIP: conn.DestIP,
ICMPType: conn.ICMPType,
ICMPCode: conn.ICMPCode,
+ RxPackets: conn.PacketsRx.Load(),
+ TxPackets: conn.PacketsTx.Load(),
+ RxBytes: conn.BytesRx.Load(),
+ TxBytes: conn.BytesTx.Load(),
})
}
-func (t *ICMPTracker) sendStartEvent(direction nftypes.Direction, key ICMPConnKey, typ, code uint8) {
- t.flowLogger.StoreEvent(nftypes.EventFields{
+func (t *ICMPTracker) sendStartEvent(direction nftypes.Direction, srcIP netip.Addr, dstIP netip.Addr, typ uint8, code uint8, ruleID []byte, size int) {
+ fields := nftypes.EventFields{
FlowID: uuid.New(),
Type: nftypes.TypeStart,
+ RuleID: ruleID,
Direction: direction,
Protocol: nftypes.ICMP,
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
+ SourceIP: srcIP,
+ DestIP: dstIP,
ICMPType: typ,
ICMPCode: code,
- })
-}
-
-// makeICMPKey creates an ICMP connection key
-func makeICMPKey(srcIP net.IP, dstIP net.IP, id uint16, seq uint16) ICMPConnKey {
- srcAddr, _ := netip.AddrFromSlice(srcIP)
- dstAddr, _ := netip.AddrFromSlice(dstIP)
- return ICMPConnKey{
- SrcIP: srcAddr,
- DstIP: dstAddr,
- ID: id,
- Sequence: seq,
}
+ if direction == nftypes.Ingress {
+ fields.RxPackets = 1
+ fields.RxBytes = uint64(size)
+ } else {
+ fields.TxPackets = 1
+ fields.TxBytes = uint64(size)
+ }
+ t.flowLogger.StoreEvent(fields)
}
diff --git a/client/firewall/uspfilter/conntrack/icmp_test.go b/client/firewall/uspfilter/conntrack/icmp_test.go
index b8328ae94..5a7b36a36 100644
--- a/client/firewall/uspfilter/conntrack/icmp_test.go
+++ b/client/firewall/uspfilter/conntrack/icmp_test.go
@@ -1,7 +1,7 @@
package conntrack
import (
- "net"
+ "net/netip"
"testing"
)
@@ -10,12 +10,12 @@ func BenchmarkICMPTracker(b *testing.B) {
tracker := NewICMPTracker(DefaultICMPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), uint16(i%65535), 0)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 0, 0)
}
})
@@ -23,17 +23,17 @@ func BenchmarkICMPTracker(b *testing.B) {
tracker := NewICMPTracker(DefaultICMPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
// Pre-populate some connections
for i := 0; i < 1000; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i), uint16(i), 0)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i), 0, 0)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracker.IsValidInbound(dstIP, srcIP, uint16(i%1000), uint16(i%1000), 0)
+ tracker.IsValidInbound(dstIP, srcIP, uint16(i%1000), 0, 0)
}
})
}
diff --git a/client/firewall/uspfilter/conntrack/tcp.go b/client/firewall/uspfilter/conntrack/tcp.go
index acf34c49d..a1f17966a 100644
--- a/client/firewall/uspfilter/conntrack/tcp.go
+++ b/client/firewall/uspfilter/conntrack/tcp.go
@@ -3,7 +3,8 @@ package conntrack
// TODO: Send RST packets for invalid/timed-out connections
import (
- "net"
+ "context"
+ "net/netip"
"sync"
"sync/atomic"
"time"
@@ -88,6 +89,8 @@ const (
// TCPConnTrack represents a TCP connection state
type TCPConnTrack struct {
BaseConnTrack
+ SourcePort uint16
+ DestPort uint16
State TCPState
established atomic.Bool
tombstone atomic.Bool
@@ -120,7 +123,7 @@ type TCPTracker struct {
connections map[ConnKey]*TCPConnTrack
mutex sync.RWMutex
cleanupTicker *time.Ticker
- done chan struct{}
+ tickerCancel context.CancelFunc
timeout time.Duration
flowLogger nftypes.FlowLogger
}
@@ -131,21 +134,28 @@ func NewTCPTracker(timeout time.Duration, logger *nblog.Logger, flowLogger nftyp
timeout = DefaultTCPTimeout
}
+ ctx, cancel := context.WithCancel(context.Background())
+
tracker := &TCPTracker{
logger: logger,
connections: make(map[ConnKey]*TCPConnTrack),
cleanupTicker: time.NewTicker(TCPCleanupInterval),
- done: make(chan struct{}),
+ tickerCancel: cancel,
timeout: timeout,
flowLogger: flowLogger,
}
- go tracker.cleanupRoutine()
+ go tracker.cleanupRoutine(ctx)
return tracker
}
-func (t *TCPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16, flags uint8) (ConnKey, bool) {
- key := makeConnKey(srcIP, dstIP, srcPort, dstPort)
+func (t *TCPTracker) updateIfExists(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, flags uint8, direction nftypes.Direction, size int) (ConnKey, bool) {
+ key := ConnKey{
+ SrcIP: srcIP,
+ DstIP: dstIP,
+ SrcPort: srcPort,
+ DstPort: dstPort,
+ }
t.mutex.RLock()
conn, exists := t.connections[key]
@@ -154,9 +164,10 @@ func (t *TCPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, srcPort uint16,
if exists {
conn.Lock()
t.updateState(key, conn, flags, conn.Direction == nftypes.Egress)
- conn.UpdateLastSeen()
conn.Unlock()
+ conn.UpdateCounters(direction, size)
+
return key, true
}
@@ -164,37 +175,36 @@ func (t *TCPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, srcPort uint16,
}
// TrackOutbound records an outbound TCP connection
-func (t *TCPTracker) TrackOutbound(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16, flags uint8) {
- if _, exists := t.updateIfExists(dstIP, srcIP, dstPort, srcPort, flags); !exists {
+func (t *TCPTracker) TrackOutbound(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, flags uint8, size int) {
+ if _, exists := t.updateIfExists(dstIP, srcIP, dstPort, srcPort, flags, 0, 0); !exists {
// if (inverted direction) conn is not tracked, track this direction
- t.track(srcIP, dstIP, srcPort, dstPort, flags, nftypes.Egress)
+ t.track(srcIP, dstIP, srcPort, dstPort, flags, nftypes.Egress, nil, size)
}
}
// TrackInbound processes an inbound TCP packet and updates connection state
-func (t *TCPTracker) TrackInbound(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16, flags uint8) {
- t.track(srcIP, dstIP, srcPort, dstPort, flags, nftypes.Ingress)
+func (t *TCPTracker) TrackInbound(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, flags uint8, ruleID []byte, size int) {
+ t.track(srcIP, dstIP, srcPort, dstPort, flags, nftypes.Ingress, ruleID, size)
}
// track is the common implementation for tracking both inbound and outbound connections
-func (t *TCPTracker) track(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16, flags uint8, direction nftypes.Direction) {
- key, exists := t.updateIfExists(srcIP, dstIP, srcPort, dstPort, flags)
+func (t *TCPTracker) track(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, flags uint8, direction nftypes.Direction, ruleID []byte, size int) {
+ key, exists := t.updateIfExists(srcIP, dstIP, srcPort, dstPort, flags, direction, size)
if exists {
return
}
conn := &TCPConnTrack{
BaseConnTrack: BaseConnTrack{
- FlowId: uuid.New(),
- Direction: direction,
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
- SourcePort: srcPort,
- DestPort: dstPort,
+ FlowId: uuid.New(),
+ Direction: direction,
+ SourceIP: srcIP,
+ DestIP: dstIP,
},
+ SourcePort: srcPort,
+ DestPort: dstPort,
}
- conn.UpdateLastSeen()
conn.established.Store(false)
conn.tombstone.Store(false)
@@ -205,12 +215,17 @@ func (t *TCPTracker) track(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort u
t.connections[key] = conn
t.mutex.Unlock()
- t.sendEvent(nftypes.TypeStart, key, conn)
+ t.sendEvent(nftypes.TypeStart, conn, ruleID)
}
// IsValidInbound checks if an inbound TCP packet matches a tracked connection
-func (t *TCPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16, flags uint8) bool {
- key := makeConnKey(dstIP, srcIP, dstPort, srcPort)
+func (t *TCPTracker) IsValidInbound(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, flags uint8, size int) bool {
+ key := ConnKey{
+ SrcIP: dstIP,
+ DstIP: srcIP,
+ SrcPort: dstPort,
+ DstPort: srcPort,
+ }
t.mutex.RLock()
conn, exists := t.connections[key]
@@ -231,15 +246,15 @@ func (t *TCPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, srcPort uint16,
conn.State = TCPStateClosed
conn.SetEstablished(false)
conn.Unlock()
+ conn.UpdateCounters(nftypes.Ingress, size)
t.logger.Trace("TCP connection reset: %s", key)
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
return true
}
conn.Lock()
t.updateState(key, conn, flags, false)
- conn.UpdateLastSeen()
isEstablished := conn.IsEstablished()
isValidState := t.isValidStateForFlags(conn.State, flags)
conn.Unlock()
@@ -249,6 +264,8 @@ func (t *TCPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, srcPort uint16,
// updateState updates the TCP connection state based on flags
func (t *TCPTracker) updateState(key ConnKey, conn *TCPConnTrack, flags uint8, isOutbound bool) {
+ conn.UpdateLastSeen()
+
state := conn.State
defer func() {
if state != conn.State {
@@ -287,17 +304,24 @@ func (t *TCPTracker) updateState(key ConnKey, conn *TCPConnTrack, flags uint8, i
conn.State = TCPStateCloseWait
}
conn.SetEstablished(false)
+ } else if flags&TCPRst != 0 {
+ conn.State = TCPStateClosed
+ conn.SetTombstone()
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
case TCPStateFinWait1:
switch {
case flags&TCPFin != 0 && flags&TCPAck != 0:
- // Simultaneous close - both sides sent FIN
conn.State = TCPStateClosing
case flags&TCPFin != 0:
conn.State = TCPStateFinWait2
case flags&TCPAck != 0:
conn.State = TCPStateFinWait2
+ case flags&TCPRst != 0:
+ conn.State = TCPStateClosed
+ conn.SetTombstone()
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
case TCPStateFinWait2:
@@ -305,7 +329,7 @@ func (t *TCPTracker) updateState(key ConnKey, conn *TCPConnTrack, flags uint8, i
conn.State = TCPStateTimeWait
t.logger.Trace("TCP connection %s completed", key)
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
case TCPStateClosing:
@@ -314,7 +338,7 @@ func (t *TCPTracker) updateState(key ConnKey, conn *TCPConnTrack, flags uint8, i
// Keep established = false from previous state
t.logger.Trace("TCP connection %s closed (simultaneous)", key)
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
case TCPStateCloseWait:
@@ -328,7 +352,7 @@ func (t *TCPTracker) updateState(key ConnKey, conn *TCPConnTrack, flags uint8, i
conn.SetTombstone()
// Send close event for gracefully closed connections
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
t.logger.Trace("TCP connection %s closed gracefully", key)
}
}
@@ -375,12 +399,14 @@ func (t *TCPTracker) isValidStateForFlags(state TCPState, flags uint8) bool {
return false
}
-func (t *TCPTracker) cleanupRoutine() {
+func (t *TCPTracker) cleanupRoutine(ctx context.Context) {
+ defer t.cleanupTicker.Stop()
+
for {
select {
case <-t.cleanupTicker.C:
t.cleanup()
- case <-t.done:
+ case <-ctx.Done():
return
}
}
@@ -411,11 +437,11 @@ func (t *TCPTracker) cleanup() {
// Return IPs to pool
delete(t.connections, key)
- t.logger.Trace("Cleaned up timed-out TCP connection %s", &key)
+ t.logger.Trace("Cleaned up timed-out TCP connection %s", key)
// event already handled by state change
if conn.State != TCPStateTimeWait {
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
}
}
@@ -423,8 +449,7 @@ func (t *TCPTracker) cleanup() {
// Close stops the cleanup routine and releases resources
func (t *TCPTracker) Close() {
- t.cleanupTicker.Stop()
- close(t.done)
+ t.tickerCancel()
// Clean up all remaining IPs
t.mutex.Lock()
@@ -446,15 +471,20 @@ func isValidFlagCombination(flags uint8) bool {
return true
}
-func (t *TCPTracker) sendEvent(typ nftypes.Type, key ConnKey, conn *TCPConnTrack) {
+func (t *TCPTracker) sendEvent(typ nftypes.Type, conn *TCPConnTrack, ruleID []byte) {
t.flowLogger.StoreEvent(nftypes.EventFields{
FlowID: conn.FlowId,
Type: typ,
+ RuleID: ruleID,
Direction: conn.Direction,
Protocol: nftypes.TCP,
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
- SourcePort: key.SrcPort,
- DestPort: key.DstPort,
+ SourceIP: conn.SourceIP,
+ DestIP: conn.DestIP,
+ SourcePort: conn.SourcePort,
+ DestPort: conn.DestPort,
+ RxPackets: conn.PacketsRx.Load(),
+ TxPackets: conn.PacketsTx.Load(),
+ RxBytes: conn.BytesRx.Load(),
+ TxBytes: conn.BytesTx.Load(),
})
}
diff --git a/client/firewall/uspfilter/conntrack/tcp_test.go b/client/firewall/uspfilter/conntrack/tcp_test.go
index 200d77501..96558583d 100644
--- a/client/firewall/uspfilter/conntrack/tcp_test.go
+++ b/client/firewall/uspfilter/conntrack/tcp_test.go
@@ -1,7 +1,7 @@
package conntrack
import (
- "net"
+ "net/netip"
"testing"
"time"
@@ -12,8 +12,8 @@ func TestTCPStateMachine(t *testing.T) {
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("100.64.0.1")
- dstIP := net.ParseIP("100.64.0.2")
+ srcIP := netip.MustParseAddr("100.64.0.1")
+ dstIP := netip.MustParseAddr("100.64.0.2")
srcPort := uint16(12345)
dstPort := uint16(80)
@@ -58,7 +58,7 @@ func TestTCPStateMachine(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- isValid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, tt.flags)
+ isValid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, tt.flags, 0)
require.Equal(t, !tt.wantDrop, isValid, tt.desc)
})
}
@@ -76,17 +76,17 @@ func TestTCPStateMachine(t *testing.T) {
t.Helper()
// Send initial SYN
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn, 0)
// Receive SYN-ACK
- valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck)
+ valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck, 0)
require.True(t, valid, "SYN-ACK should be allowed")
// Send ACK
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 0)
// Test data transfer
- valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPPush|TCPAck)
+ valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPPush|TCPAck, 0)
require.True(t, valid, "Data should be allowed after handshake")
},
},
@@ -99,18 +99,18 @@ func TestTCPStateMachine(t *testing.T) {
establishConnection(t, tracker, srcIP, dstIP, srcPort, dstPort)
// Send FIN
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPFin|TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPFin|TCPAck, 0)
// Receive ACK for FIN
- valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck)
+ valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck, 0)
require.True(t, valid, "ACK for FIN should be allowed")
// Receive FIN from other side
- valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPFin|TCPAck)
+ valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPFin|TCPAck, 0)
require.True(t, valid, "FIN should be allowed")
// Send final ACK
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 0)
},
},
{
@@ -122,7 +122,7 @@ func TestTCPStateMachine(t *testing.T) {
establishConnection(t, tracker, srcIP, dstIP, srcPort, dstPort)
// Receive RST
- valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst)
+ valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst, 0)
require.True(t, valid, "RST should be allowed for established connection")
// Connection is logically dead but we don't enforce blocking subsequent packets
@@ -138,13 +138,13 @@ func TestTCPStateMachine(t *testing.T) {
establishConnection(t, tracker, srcIP, dstIP, srcPort, dstPort)
// Both sides send FIN+ACK
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPFin|TCPAck)
- valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPFin|TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPFin|TCPAck, 0)
+ valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPFin|TCPAck, 0)
require.True(t, valid, "Simultaneous FIN should be allowed")
// Both sides send final ACK
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck)
- valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 0)
+ valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck, 0)
require.True(t, valid, "Final ACKs should be allowed")
},
},
@@ -165,8 +165,8 @@ func TestRSTHandling(t *testing.T) {
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("100.64.0.1")
- dstIP := net.ParseIP("100.64.0.2")
+ srcIP := netip.MustParseAddr("100.64.0.1")
+ dstIP := netip.MustParseAddr("100.64.0.2")
srcPort := uint16(12345)
dstPort := uint16(80)
@@ -181,12 +181,12 @@ func TestRSTHandling(t *testing.T) {
name: "RST in established",
setupState: func() {
// Establish connection first
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn)
- tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck)
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn, 0)
+ tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck, 0)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 0)
},
sendRST: func() {
- tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst)
+ tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst, 0)
},
wantValid: true,
desc: "Should accept RST for established connection",
@@ -195,7 +195,7 @@ func TestRSTHandling(t *testing.T) {
name: "RST without connection",
setupState: func() {},
sendRST: func() {
- tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst)
+ tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst, 0)
},
wantValid: false,
desc: "Should reject RST without connection",
@@ -208,7 +208,12 @@ func TestRSTHandling(t *testing.T) {
tt.sendRST()
// Verify connection state is as expected
- key := makeConnKey(srcIP, dstIP, srcPort, dstPort)
+ key := ConnKey{
+ SrcIP: srcIP,
+ DstIP: dstIP,
+ SrcPort: srcPort,
+ DstPort: dstPort,
+ }
conn := tracker.connections[key]
if tt.wantValid {
require.NotNil(t, conn)
@@ -220,15 +225,15 @@ func TestRSTHandling(t *testing.T) {
}
// Helper to establish a TCP connection
-func establishConnection(t *testing.T, tracker *TCPTracker, srcIP, dstIP net.IP, srcPort, dstPort uint16) {
+func establishConnection(t *testing.T, tracker *TCPTracker, srcIP, dstIP netip.Addr, srcPort, dstPort uint16) {
t.Helper()
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn, 0)
- valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck)
+ valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck, 0)
require.True(t, valid, "SYN-ACK should be allowed")
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 0)
}
func BenchmarkTCPTracker(b *testing.B) {
@@ -236,12 +241,12 @@ func BenchmarkTCPTracker(b *testing.B) {
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 80, TCPSyn)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 80, TCPSyn, 0)
}
})
@@ -249,17 +254,17 @@ func BenchmarkTCPTracker(b *testing.B) {
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
// Pre-populate some connections
for i := 0; i < 1000; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i), 80, TCPSyn)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i), 80, TCPSyn, 0)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracker.IsValidInbound(dstIP, srcIP, 80, uint16(i%1000), TCPAck)
+ tracker.IsValidInbound(dstIP, srcIP, 80, uint16(i%1000), TCPAck, 0)
}
})
@@ -267,16 +272,16 @@ func BenchmarkTCPTracker(b *testing.B) {
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
if i%2 == 0 {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 80, TCPSyn)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 80, TCPSyn, 0)
} else {
- tracker.IsValidInbound(dstIP, srcIP, 80, uint16(i%65535), TCPAck)
+ tracker.IsValidInbound(dstIP, srcIP, 80, uint16(i%65535), TCPAck, 0)
}
i++
}
@@ -291,10 +296,10 @@ func BenchmarkCleanup(b *testing.B) {
defer tracker.Close()
// Pre-populate with expired connections
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
for i := 0; i < 10000; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i), 80, TCPSyn)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i), 80, TCPSyn, 0)
}
// Wait for connections to expire
diff --git a/client/firewall/uspfilter/conntrack/udp.go b/client/firewall/uspfilter/conntrack/udp.go
index 922db371d..7ca493a9d 100644
--- a/client/firewall/uspfilter/conntrack/udp.go
+++ b/client/firewall/uspfilter/conntrack/udp.go
@@ -1,7 +1,8 @@
package conntrack
import (
- "net"
+ "context"
+ "net/netip"
"sync"
"time"
@@ -21,6 +22,8 @@ const (
// UDPConnTrack represents a UDP connection state
type UDPConnTrack struct {
BaseConnTrack
+ SourcePort uint16
+ DestPort uint16
}
// UDPTracker manages UDP connection states
@@ -29,8 +32,8 @@ type UDPTracker struct {
connections map[ConnKey]*UDPConnTrack
timeout time.Duration
cleanupTicker *time.Ticker
+ tickerCancel context.CancelFunc
mutex sync.RWMutex
- done chan struct{}
flowLogger nftypes.FlowLogger
}
@@ -40,34 +43,41 @@ func NewUDPTracker(timeout time.Duration, logger *nblog.Logger, flowLogger nftyp
timeout = DefaultUDPTimeout
}
+ ctx, cancel := context.WithCancel(context.Background())
+
tracker := &UDPTracker{
logger: logger,
connections: make(map[ConnKey]*UDPConnTrack),
timeout: timeout,
cleanupTicker: time.NewTicker(UDPCleanupInterval),
- done: make(chan struct{}),
+ tickerCancel: cancel,
flowLogger: flowLogger,
}
- go tracker.cleanupRoutine()
+ go tracker.cleanupRoutine(ctx)
return tracker
}
// TrackOutbound records an outbound UDP connection
-func (t *UDPTracker) TrackOutbound(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16) {
- if _, exists := t.updateIfExists(dstIP, srcIP, dstPort, srcPort); !exists {
+func (t *UDPTracker) TrackOutbound(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, size int) {
+ if _, exists := t.updateIfExists(dstIP, srcIP, dstPort, srcPort, nftypes.Egress, size); !exists {
// if (inverted direction) conn is not tracked, track this direction
- t.track(srcIP, dstIP, srcPort, dstPort, nftypes.Egress)
+ t.track(srcIP, dstIP, srcPort, dstPort, nftypes.Egress, nil, size)
}
}
// TrackInbound records an inbound UDP connection
-func (t *UDPTracker) TrackInbound(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16) {
- t.track(srcIP, dstIP, srcPort, dstPort, nftypes.Ingress)
+func (t *UDPTracker) TrackInbound(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, ruleID []byte, size int) {
+ t.track(srcIP, dstIP, srcPort, dstPort, nftypes.Ingress, ruleID, size)
}
-func (t *UDPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16) (ConnKey, bool) {
- key := makeConnKey(srcIP, dstIP, srcPort, dstPort)
+func (t *UDPTracker) updateIfExists(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, direction nftypes.Direction, size int) (ConnKey, bool) {
+ key := ConnKey{
+ SrcIP: srcIP,
+ DstIP: dstIP,
+ SrcPort: srcPort,
+ DstPort: dstPort,
+ }
t.mutex.RLock()
conn, exists := t.connections[key]
@@ -75,6 +85,7 @@ func (t *UDPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, srcPort uint16,
if exists {
conn.UpdateLastSeen()
+ conn.UpdateCounters(direction, size)
return key, true
}
@@ -82,21 +93,21 @@ func (t *UDPTracker) updateIfExists(srcIP net.IP, dstIP net.IP, srcPort uint16,
}
// track is the common implementation for tracking both inbound and outbound connections
-func (t *UDPTracker) track(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16, direction nftypes.Direction) {
- key, exists := t.updateIfExists(srcIP, dstIP, srcPort, dstPort)
+func (t *UDPTracker) track(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, direction nftypes.Direction, ruleID []byte, size int) {
+ key, exists := t.updateIfExists(srcIP, dstIP, srcPort, dstPort, direction, size)
if exists {
return
}
conn := &UDPConnTrack{
BaseConnTrack: BaseConnTrack{
- FlowId: uuid.New(),
- Direction: direction,
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
- SourcePort: srcPort,
- DestPort: dstPort,
+ FlowId: uuid.New(),
+ Direction: direction,
+ SourceIP: srcIP,
+ DestIP: dstIP,
},
+ SourcePort: srcPort,
+ DestPort: dstPort,
}
conn.UpdateLastSeen()
@@ -105,12 +116,17 @@ func (t *UDPTracker) track(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort u
t.mutex.Unlock()
t.logger.Trace("New %s UDP connection: %s", direction, key)
- t.sendEvent(nftypes.TypeStart, key, conn)
+ t.sendEvent(nftypes.TypeStart, conn, ruleID)
}
// IsValidInbound checks if an inbound packet matches a tracked connection
-func (t *UDPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, srcPort uint16, dstPort uint16) bool {
- key := makeConnKey(dstIP, srcIP, dstPort, srcPort)
+func (t *UDPTracker) IsValidInbound(srcIP netip.Addr, dstIP netip.Addr, srcPort uint16, dstPort uint16, size int) bool {
+ key := ConnKey{
+ SrcIP: dstIP,
+ DstIP: srcIP,
+ SrcPort: dstPort,
+ DstPort: srcPort,
+ }
t.mutex.RLock()
conn, exists := t.connections[key]
@@ -121,17 +137,20 @@ func (t *UDPTracker) IsValidInbound(srcIP net.IP, dstIP net.IP, srcPort uint16,
}
conn.UpdateLastSeen()
+ conn.UpdateCounters(nftypes.Ingress, size)
return true
}
// cleanupRoutine periodically removes stale connections
-func (t *UDPTracker) cleanupRoutine() {
+func (t *UDPTracker) cleanupRoutine(ctx context.Context) {
+ defer t.cleanupTicker.Stop()
+
for {
select {
case <-t.cleanupTicker.C:
t.cleanup()
- case <-t.done:
+ case <-ctx.Done():
return
}
}
@@ -145,16 +164,16 @@ func (t *UDPTracker) cleanup() {
if conn.timeoutExceeded(t.timeout) {
delete(t.connections, key)
- t.logger.Trace("Removed UDP connection %s (timeout)", key)
- t.sendEvent(nftypes.TypeEnd, key, conn)
+ t.logger.Trace("Removed UDP connection %s (timeout) [in: %d Pkts/%d B out: %d Pkts/%d B]",
+ key, conn.PacketsRx.Load(), conn.BytesRx.Load(), conn.PacketsTx.Load(), conn.BytesTx.Load())
+ t.sendEvent(nftypes.TypeEnd, conn, nil)
}
}
}
// Close stops the cleanup routine and releases resources
func (t *UDPTracker) Close() {
- t.cleanupTicker.Stop()
- close(t.done)
+ t.tickerCancel()
t.mutex.Lock()
t.connections = nil
@@ -162,11 +181,16 @@ func (t *UDPTracker) Close() {
}
// GetConnection safely retrieves a connection state
-func (t *UDPTracker) GetConnection(srcIP net.IP, srcPort uint16, dstIP net.IP, dstPort uint16) (*UDPConnTrack, bool) {
+func (t *UDPTracker) GetConnection(srcIP netip.Addr, srcPort uint16, dstIP netip.Addr, dstPort uint16) (*UDPConnTrack, bool) {
t.mutex.RLock()
defer t.mutex.RUnlock()
- key := makeConnKey(srcIP, dstIP, srcPort, dstPort)
+ key := ConnKey{
+ SrcIP: srcIP,
+ DstIP: dstIP,
+ SrcPort: srcPort,
+ DstPort: dstPort,
+ }
conn, exists := t.connections[key]
return conn, exists
}
@@ -176,15 +200,20 @@ func (t *UDPTracker) Timeout() time.Duration {
return t.timeout
}
-func (t *UDPTracker) sendEvent(typ nftypes.Type, key ConnKey, conn *UDPConnTrack) {
+func (t *UDPTracker) sendEvent(typ nftypes.Type, conn *UDPConnTrack, ruleID []byte) {
t.flowLogger.StoreEvent(nftypes.EventFields{
FlowID: conn.FlowId,
Type: typ,
+ RuleID: ruleID,
Direction: conn.Direction,
Protocol: nftypes.UDP,
- SourceIP: key.SrcIP,
- DestIP: key.DstIP,
- SourcePort: key.SrcPort,
- DestPort: key.DstPort,
+ SourceIP: conn.SourceIP,
+ DestIP: conn.DestIP,
+ SourcePort: conn.SourcePort,
+ DestPort: conn.DestPort,
+ RxPackets: conn.PacketsRx.Load(),
+ TxPackets: conn.PacketsTx.Load(),
+ RxBytes: conn.BytesRx.Load(),
+ TxBytes: conn.BytesTx.Load(),
})
}
diff --git a/client/firewall/uspfilter/conntrack/udp_test.go b/client/firewall/uspfilter/conntrack/udp_test.go
index 29c7111fd..7ad1e0e4b 100644
--- a/client/firewall/uspfilter/conntrack/udp_test.go
+++ b/client/firewall/uspfilter/conntrack/udp_test.go
@@ -1,7 +1,7 @@
package conntrack
import (
- "net"
+ "context"
"net/netip"
"testing"
"time"
@@ -35,7 +35,7 @@ func TestNewUDPTracker(t *testing.T) {
assert.Equal(t, tt.wantTimeout, tracker.timeout)
assert.NotNil(t, tracker.connections)
assert.NotNil(t, tracker.cleanupTicker)
- assert.NotNil(t, tracker.done)
+ assert.NotNil(t, tracker.tickerCancel)
})
}
}
@@ -49,10 +49,15 @@ func TestUDPTracker_TrackOutbound(t *testing.T) {
srcPort := uint16(12345)
dstPort := uint16(53)
- tracker.TrackOutbound(srcIP.AsSlice(), dstIP.AsSlice(), srcPort, dstPort)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, 0)
// Verify connection was tracked
- key := makeConnKey(srcIP.AsSlice(), dstIP.AsSlice(), srcPort, dstPort)
+ key := ConnKey{
+ SrcIP: srcIP,
+ DstIP: dstIP,
+ SrcPort: srcPort,
+ DstPort: dstPort,
+ }
conn, exists := tracker.connections[key]
require.True(t, exists)
assert.True(t, conn.SourceIP.Compare(srcIP) == 0)
@@ -66,18 +71,18 @@ func TestUDPTracker_IsValidInbound(t *testing.T) {
tracker := NewUDPTracker(1*time.Second, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.2")
- dstIP := net.ParseIP("192.168.1.3")
+ srcIP := netip.MustParseAddr("192.168.1.2")
+ dstIP := netip.MustParseAddr("192.168.1.3")
srcPort := uint16(12345)
dstPort := uint16(53)
// Track outbound connection
- tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort)
+ tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, 0)
tests := []struct {
name string
- srcIP net.IP
- dstIP net.IP
+ srcIP netip.Addr
+ dstIP netip.Addr
srcPort uint16
dstPort uint16
sleep time.Duration
@@ -94,7 +99,7 @@ func TestUDPTracker_IsValidInbound(t *testing.T) {
},
{
name: "invalid source IP",
- srcIP: net.ParseIP("192.168.1.4"),
+ srcIP: netip.MustParseAddr("192.168.1.4"),
dstIP: srcIP,
srcPort: dstPort,
dstPort: srcPort,
@@ -104,7 +109,7 @@ func TestUDPTracker_IsValidInbound(t *testing.T) {
{
name: "invalid destination IP",
srcIP: dstIP,
- dstIP: net.ParseIP("192.168.1.4"),
+ dstIP: netip.MustParseAddr("192.168.1.4"),
srcPort: dstPort,
dstPort: srcPort,
sleep: 0,
@@ -144,7 +149,7 @@ func TestUDPTracker_IsValidInbound(t *testing.T) {
if tt.sleep > 0 {
time.Sleep(tt.sleep)
}
- got := tracker.IsValidInbound(tt.srcIP, tt.dstIP, tt.srcPort, tt.dstPort)
+ got := tracker.IsValidInbound(tt.srcIP, tt.dstIP, tt.srcPort, tt.dstPort, 0)
assert.Equal(t, tt.want, got)
})
}
@@ -155,42 +160,45 @@ func TestUDPTracker_Cleanup(t *testing.T) {
timeout := 50 * time.Millisecond
cleanupInterval := 25 * time.Millisecond
+ ctx, tickerCancel := context.WithCancel(context.Background())
+ defer tickerCancel()
+
// Create tracker with custom cleanup interval
tracker := &UDPTracker{
connections: make(map[ConnKey]*UDPConnTrack),
timeout: timeout,
cleanupTicker: time.NewTicker(cleanupInterval),
- done: make(chan struct{}),
+ tickerCancel: tickerCancel,
logger: logger,
flowLogger: flowLogger,
}
// Start cleanup routine
- go tracker.cleanupRoutine()
+ go tracker.cleanupRoutine(ctx)
// Add some connections
connections := []struct {
- srcIP net.IP
- dstIP net.IP
+ srcIP netip.Addr
+ dstIP netip.Addr
srcPort uint16
dstPort uint16
}{
{
- srcIP: net.ParseIP("192.168.1.2"),
- dstIP: net.ParseIP("192.168.1.3"),
+ srcIP: netip.MustParseAddr("192.168.1.2"),
+ dstIP: netip.MustParseAddr("192.168.1.3"),
srcPort: 12345,
dstPort: 53,
},
{
- srcIP: net.ParseIP("192.168.1.4"),
- dstIP: net.ParseIP("192.168.1.5"),
+ srcIP: netip.MustParseAddr("192.168.1.4"),
+ dstIP: netip.MustParseAddr("192.168.1.5"),
srcPort: 12346,
dstPort: 53,
},
}
for _, conn := range connections {
- tracker.TrackOutbound(conn.srcIP, conn.dstIP, conn.srcPort, conn.dstPort)
+ tracker.TrackOutbound(conn.srcIP, conn.dstIP, conn.srcPort, conn.dstPort, 0)
}
// Verify initial connections
@@ -215,12 +223,12 @@ func BenchmarkUDPTracker(b *testing.B) {
tracker := NewUDPTracker(DefaultUDPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 80)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i%65535), 80, 0)
}
})
@@ -228,17 +236,17 @@ func BenchmarkUDPTracker(b *testing.B) {
tracker := NewUDPTracker(DefaultUDPTimeout, logger, flowLogger)
defer tracker.Close()
- srcIP := net.ParseIP("192.168.1.1")
- dstIP := net.ParseIP("192.168.1.2")
+ srcIP := netip.MustParseAddr("192.168.1.1")
+ dstIP := netip.MustParseAddr("192.168.1.2")
// Pre-populate some connections
for i := 0; i < 1000; i++ {
- tracker.TrackOutbound(srcIP, dstIP, uint16(i), 80)
+ tracker.TrackOutbound(srcIP, dstIP, uint16(i), 80, 0)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracker.IsValidInbound(dstIP, srcIP, 80, uint16(i%1000))
+ tracker.IsValidInbound(dstIP, srcIP, 80, uint16(i%1000), 0)
}
})
}
diff --git a/client/firewall/uspfilter/forwarder/icmp.go b/client/firewall/uspfilter/forwarder/icmp.go
index 3d7e8857b..a21ec2c87 100644
--- a/client/firewall/uspfilter/forwarder/icmp.go
+++ b/client/firewall/uspfilter/forwarder/icmp.go
@@ -15,13 +15,16 @@ import (
// handleICMP handles ICMP packets from the network stack
func (f *Forwarder) handleICMP(id stack.TransportEndpointID, pkt stack.PacketBufferPtr) bool {
- flowID := uuid.New()
-
- // Extract ICMP header to get type and code
icmpHdr := header.ICMPv4(pkt.TransportHeader().View().AsSlice())
icmpType := uint8(icmpHdr.Type())
icmpCode := uint8(icmpHdr.Code())
+ if header.ICMPv4Type(icmpType) == header.ICMPv4EchoReply {
+ // dont process our own replies
+ return true
+ }
+
+ flowID := uuid.New()
f.sendICMPEvent(nftypes.TypeStart, flowID, id, icmpType, icmpCode)
ctx, cancel := context.WithTimeout(f.ctx, 5*time.Second)
@@ -33,8 +36,6 @@ func (f *Forwarder) handleICMP(id stack.TransportEndpointID, pkt stack.PacketBuf
if err != nil {
f.logger.Error("Failed to create ICMP socket for %v: %v", epID(id), err)
- f.sendICMPEvent(nftypes.TypeEnd, flowID, id, icmpType, icmpCode)
-
// This will make netstack reply on behalf of the original destination, that's ok for now
return false
}
@@ -42,30 +43,15 @@ func (f *Forwarder) handleICMP(id stack.TransportEndpointID, pkt stack.PacketBuf
if err := conn.Close(); err != nil {
f.logger.Debug("Failed to close ICMP socket: %v", err)
}
-
- f.sendICMPEvent(nftypes.TypeEnd, flowID, id, icmpType, icmpCode)
}()
dstIP := f.determineDialAddr(id.LocalAddress)
dst := &net.IPAddr{IP: dstIP}
- // Get the complete ICMP message (header + data)
fullPacket := stack.PayloadSince(pkt.TransportHeader())
payload := fullPacket.AsSlice()
- // For Echo Requests, send and handle response
- switch icmpHdr.Type() {
- case header.ICMPv4Echo:
- return f.handleEchoResponse(icmpHdr, payload, dst, conn, id, flowID)
- case header.ICMPv4EchoReply:
- // dont process our own replies
- return true
- default:
- }
-
- // For other ICMP types (Time Exceeded, Destination Unreachable, etc)
- _, err = conn.WriteTo(payload, dst)
- if err != nil {
+ if _, err = conn.WriteTo(payload, dst); err != nil {
f.logger.Error("Failed to write ICMP packet for %v: %v", epID(id), err)
return true
}
@@ -73,21 +59,20 @@ func (f *Forwarder) handleICMP(id stack.TransportEndpointID, pkt stack.PacketBuf
f.logger.Trace("Forwarded ICMP packet %v type %v code %v",
epID(id), icmpHdr.Type(), icmpHdr.Code())
+ // For Echo Requests, send and handle response
+ if header.ICMPv4Type(icmpType) == header.ICMPv4Echo {
+ f.handleEchoResponse(icmpHdr, conn, id)
+ f.sendICMPEvent(nftypes.TypeEnd, flowID, id, icmpType, icmpCode)
+ }
+
+ // For other ICMP types (Time Exceeded, Destination Unreachable, etc) do nothing
return true
}
-func (f *Forwarder) handleEchoResponse(icmpHdr header.ICMPv4, payload []byte, dst *net.IPAddr, conn net.PacketConn, id stack.TransportEndpointID, flowID uuid.UUID) bool {
- if _, err := conn.WriteTo(payload, dst); err != nil {
- f.logger.Error("Failed to write ICMP packet for %v: %v", epID(id), err)
- return true
- }
-
- f.logger.Trace("Forwarded ICMP packet %v type %v code %v",
- epID(id), icmpHdr.Type(), icmpHdr.Code())
-
+func (f *Forwarder) handleEchoResponse(icmpHdr header.ICMPv4, conn net.PacketConn, id stack.TransportEndpointID) {
if err := conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil {
f.logger.Error("Failed to set read deadline for ICMP response: %v", err)
- return true
+ return
}
response := make([]byte, f.endpoint.mtu)
@@ -96,7 +81,7 @@ func (f *Forwarder) handleEchoResponse(icmpHdr header.ICMPv4, payload []byte, ds
if !isTimeout(err) {
f.logger.Error("Failed to read ICMP response: %v", err)
}
- return true
+ return
}
ipHdr := make([]byte, header.IPv4MinimumSize)
@@ -117,13 +102,11 @@ func (f *Forwarder) handleEchoResponse(icmpHdr header.ICMPv4, payload []byte, ds
if err := f.InjectIncomingPacket(fullPacket); err != nil {
f.logger.Error("Failed to inject ICMP response: %v", err)
- return true
+ return
}
f.logger.Trace("Forwarded ICMP echo reply for %v type %v code %v",
epID(id), icmpHdr.Type(), icmpHdr.Code())
-
- return true
}
// sendICMPEvent stores flow events for ICMP packets
@@ -134,9 +117,11 @@ func (f *Forwarder) sendICMPEvent(typ nftypes.Type, flowID uuid.UUID, id stack.T
Direction: nftypes.Ingress,
Protocol: nftypes.ICMP,
// TODO: handle ipv6
- SourceIP: netip.AddrFrom4(id.LocalAddress.As4()),
- DestIP: netip.AddrFrom4(id.RemoteAddress.As4()),
+ SourceIP: netip.AddrFrom4(id.RemoteAddress.As4()),
+ DestIP: netip.AddrFrom4(id.LocalAddress.As4()),
ICMPType: icmpType,
ICMPCode: icmpCode,
+
+ // TODO: get packets/bytes
})
}
diff --git a/client/firewall/uspfilter/forwarder/tcp.go b/client/firewall/uspfilter/forwarder/tcp.go
index 44aafa989..71cd457ef 100644
--- a/client/firewall/uspfilter/forwarder/tcp.go
+++ b/client/firewall/uspfilter/forwarder/tcp.go
@@ -22,7 +22,14 @@ func (f *Forwarder) handleTCP(r *tcp.ForwarderRequest) {
id := r.ID()
flowID := uuid.New()
- f.sendTCPEvent(nftypes.TypeStart, flowID, id)
+
+ f.sendTCPEvent(nftypes.TypeStart, flowID, id, nil)
+ var success bool
+ defer func() {
+ if !success {
+ f.sendTCPEvent(nftypes.TypeEnd, flowID, id, nil)
+ }
+ }()
dialAddr := fmt.Sprintf("%s:%d", f.determineDialAddr(id.LocalAddress), id.LocalPort)
@@ -51,6 +58,7 @@ func (f *Forwarder) handleTCP(r *tcp.ForwarderRequest) {
inConn := gonet.NewTCPConn(&wq, ep)
+ success = true
f.logger.Trace("forwarder: established TCP connection %v", epID(id))
go f.proxyTCP(id, inConn, outConn, ep, flowID)
@@ -66,7 +74,7 @@ func (f *Forwarder) proxyTCP(id stack.TransportEndpointID, inConn *gonet.TCPConn
}
ep.Close()
- f.sendTCPEvent(nftypes.TypeEnd, flowID, id)
+ f.sendTCPEvent(nftypes.TypeEnd, flowID, id, ep)
}()
// Create context for managing the proxy goroutines
@@ -98,17 +106,27 @@ func (f *Forwarder) proxyTCP(id stack.TransportEndpointID, inConn *gonet.TCPConn
}
}
-func (f *Forwarder) sendTCPEvent(typ nftypes.Type, flowID uuid.UUID, id stack.TransportEndpointID) {
-
- f.flowLogger.StoreEvent(nftypes.EventFields{
+func (f *Forwarder) sendTCPEvent(typ nftypes.Type, flowID uuid.UUID, id stack.TransportEndpointID, ep tcpip.Endpoint) {
+ fields := nftypes.EventFields{
FlowID: flowID,
Type: typ,
Direction: nftypes.Ingress,
- Protocol: 6,
+ Protocol: nftypes.TCP,
// TODO: handle ipv6
- SourceIP: netip.AddrFrom4(id.LocalAddress.As4()),
- DestIP: netip.AddrFrom4(id.RemoteAddress.As4()),
- SourcePort: id.LocalPort,
- DestPort: id.RemotePort,
- })
+ SourceIP: netip.AddrFrom4(id.RemoteAddress.As4()),
+ DestIP: netip.AddrFrom4(id.LocalAddress.As4()),
+ SourcePort: id.RemotePort,
+ DestPort: id.LocalPort,
+ }
+
+ if ep != nil {
+ if tcpStats, ok := ep.Stats().(*tcp.Stats); ok {
+ // fields are flipped since this is the in conn
+ // TODO: get bytes
+ fields.RxPackets = tcpStats.SegmentsSent.Value()
+ fields.TxPackets = tcpStats.SegmentsReceived.Value()
+ }
+ }
+
+ f.flowLogger.StoreEvent(fields)
}
diff --git a/client/firewall/uspfilter/forwarder/udp.go b/client/firewall/uspfilter/forwarder/udp.go
index db8aa1a2f..7ce85e2b6 100644
--- a/client/firewall/uspfilter/forwarder/udp.go
+++ b/client/firewall/uspfilter/forwarder/udp.go
@@ -89,21 +89,6 @@ func (f *udpForwarder) Stop() {
}
}
-// sendUDPEvent stores flow events for UDP connections
-func (f *udpForwarder) sendUDPEvent(typ nftypes.Type, flowID uuid.UUID, id stack.TransportEndpointID) {
- f.flowLogger.StoreEvent(nftypes.EventFields{
- FlowID: flowID,
- Type: typ,
- Direction: nftypes.Ingress,
- Protocol: 17,
- // TODO: handle ipv6
- SourceIP: netip.AddrFrom4(id.LocalAddress.As4()),
- DestIP: netip.AddrFrom4(id.RemoteAddress.As4()),
- SourcePort: id.LocalPort,
- DestPort: id.RemotePort,
- })
-}
-
// cleanup periodically removes idle UDP connections
func (f *udpForwarder) cleanup() {
ticker := time.NewTicker(time.Minute)
@@ -140,8 +125,6 @@ func (f *udpForwarder) cleanup() {
f.Unlock()
f.logger.Trace("forwarder: cleaned up idle UDP connection %v", epID(idle.id))
-
- f.sendUDPEvent(nftypes.TypeEnd, idle.conn.flowID, idle.id)
}
}
}
@@ -165,13 +148,19 @@ func (f *Forwarder) handleUDP(r *udp.ForwarderRequest) {
}
flowID := uuid.New()
- f.sendUDPEvent(nftypes.TypeStart, flowID, id)
+
+ f.sendUDPEvent(nftypes.TypeStart, flowID, id, nil)
+ var success bool
+ defer func() {
+ if !success {
+ f.sendUDPEvent(nftypes.TypeEnd, flowID, id, nil)
+ }
+ }()
dstAddr := fmt.Sprintf("%s:%d", f.determineDialAddr(id.LocalAddress), id.LocalPort)
outConn, err := (&net.Dialer{}).DialContext(f.ctx, "udp", dstAddr)
if err != nil {
f.logger.Debug("forwarder: UDP dial error for %v: %v", epID(id), err)
- f.sendUDPEvent(nftypes.TypeEnd, flowID, id)
// TODO: Send ICMP error message
return
}
@@ -184,7 +173,6 @@ func (f *Forwarder) handleUDP(r *udp.ForwarderRequest) {
if err := outConn.Close(); err != nil {
f.logger.Debug("forwarder: UDP outConn close error for %v: %v", epID(id), err)
}
- f.sendUDPEvent(nftypes.TypeEnd, flowID, id)
return
}
@@ -212,13 +200,14 @@ func (f *Forwarder) handleUDP(r *udp.ForwarderRequest) {
f.logger.Debug("forwarder: UDP outConn close error for %v: %v", epID(id), err)
}
- f.sendUDPEvent(nftypes.TypeEnd, flowID, id)
return
}
f.udpForwarder.conns[id] = pConn
f.udpForwarder.Unlock()
+ success = true
f.logger.Trace("forwarder: established UDP connection %v", epID(id))
+
go f.proxyUDP(connCtx, pConn, id, ep)
}
@@ -238,7 +227,7 @@ func (f *Forwarder) proxyUDP(ctx context.Context, pConn *udpPacketConn, id stack
delete(f.udpForwarder.conns, id)
f.udpForwarder.Unlock()
- f.sendUDPEvent(nftypes.TypeEnd, pConn.flowID, id)
+ f.sendUDPEvent(nftypes.TypeEnd, pConn.flowID, id, ep)
}()
errChan := make(chan error, 2)
@@ -264,19 +253,30 @@ func (f *Forwarder) proxyUDP(ctx context.Context, pConn *udpPacketConn, id stack
}
}
-// sendUDPEvent stores flow events for UDP connections, mirrors the TCP version
-func (f *Forwarder) sendUDPEvent(typ nftypes.Type, flowID uuid.UUID, id stack.TransportEndpointID) {
- f.flowLogger.StoreEvent(nftypes.EventFields{
+// sendUDPEvent stores flow events for UDP connections
+func (f *Forwarder) sendUDPEvent(typ nftypes.Type, flowID uuid.UUID, id stack.TransportEndpointID, ep tcpip.Endpoint) {
+ fields := nftypes.EventFields{
FlowID: flowID,
Type: typ,
Direction: nftypes.Ingress,
- Protocol: 17, // UDP protocol number
+ Protocol: nftypes.UDP,
// TODO: handle ipv6
- SourceIP: netip.AddrFrom4(id.LocalAddress.As4()),
- DestIP: netip.AddrFrom4(id.RemoteAddress.As4()),
- SourcePort: id.LocalPort,
- DestPort: id.RemotePort,
- })
+ SourceIP: netip.AddrFrom4(id.RemoteAddress.As4()),
+ DestIP: netip.AddrFrom4(id.LocalAddress.As4()),
+ SourcePort: id.RemotePort,
+ DestPort: id.LocalPort,
+ }
+
+ if ep != nil {
+ if tcpStats, ok := ep.Stats().(*tcpip.TransportEndpointStats); ok {
+ // fields are flipped since this is the in conn
+ // TODO: get bytes
+ fields.RxPackets = tcpStats.PacketsSent.Value()
+ fields.TxPackets = tcpStats.PacketsReceived.Value()
+ }
+ }
+
+ f.flowLogger.StoreEvent(fields)
}
func (c *udpPacketConn) updateLastSeen() {
diff --git a/client/firewall/uspfilter/localip.go b/client/firewall/uspfilter/localip.go
index 7664b65d5..b86d16043 100644
--- a/client/firewall/uspfilter/localip.go
+++ b/client/firewall/uspfilter/localip.go
@@ -3,6 +3,7 @@ package uspfilter
import (
"fmt"
"net"
+ "net/netip"
"sync"
log "github.com/sirupsen/logrus"
@@ -31,13 +32,9 @@ func (m *localIPManager) setBitmapBit(ip net.IP) {
m.ipv4Bitmap[high] |= 1 << (low % 32)
}
-func (m *localIPManager) checkBitmapBit(ip net.IP) bool {
- ipv4 := ip.To4()
- if ipv4 == nil {
- return false
- }
- high := (uint16(ipv4[0]) << 8) | uint16(ipv4[1])
- low := (uint16(ipv4[2]) << 8) | uint16(ipv4[3])
+func (m *localIPManager) checkBitmapBit(ip []byte) bool {
+ high := (uint16(ip[0]) << 8) | uint16(ip[1])
+ low := (uint16(ip[2]) << 8) | uint16(ip[3])
return (m.ipv4Bitmap[high] & (1 << (low % 32))) != 0
}
@@ -122,12 +119,12 @@ func (m *localIPManager) UpdateLocalIPs(iface common.IFaceMapper) (err error) {
return nil
}
-func (m *localIPManager) IsLocalIP(ip net.IP) bool {
+func (m *localIPManager) IsLocalIP(ip netip.Addr) bool {
m.mu.RLock()
defer m.mu.RUnlock()
- if ipv4 := ip.To4(); ipv4 != nil {
- return m.checkBitmapBit(ipv4)
+ if ip.Is4() {
+ return m.checkBitmapBit(ip.AsSlice())
}
return false
diff --git a/client/firewall/uspfilter/localip_test.go b/client/firewall/uspfilter/localip_test.go
index 02f41bf4f..0715ddc41 100644
--- a/client/firewall/uspfilter/localip_test.go
+++ b/client/firewall/uspfilter/localip_test.go
@@ -2,90 +2,91 @@ package uspfilter
import (
"net"
+ "net/netip"
"testing"
"github.com/stretchr/testify/require"
- "github.com/netbirdio/netbird/client/iface"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
func TestLocalIPManager(t *testing.T) {
tests := []struct {
name string
- setupAddr iface.WGAddress
- testIP net.IP
+ setupAddr wgaddr.Address
+ testIP netip.Addr
expected bool
}{
{
name: "Localhost range",
- setupAddr: iface.WGAddress{
+ setupAddr: wgaddr.Address{
IP: net.ParseIP("192.168.1.1"),
Network: &net.IPNet{
IP: net.ParseIP("192.168.1.0"),
Mask: net.CIDRMask(24, 32),
},
},
- testIP: net.ParseIP("127.0.0.2"),
+ testIP: netip.MustParseAddr("127.0.0.2"),
expected: true,
},
{
name: "Localhost standard address",
- setupAddr: iface.WGAddress{
+ setupAddr: wgaddr.Address{
IP: net.ParseIP("192.168.1.1"),
Network: &net.IPNet{
IP: net.ParseIP("192.168.1.0"),
Mask: net.CIDRMask(24, 32),
},
},
- testIP: net.ParseIP("127.0.0.1"),
+ testIP: netip.MustParseAddr("127.0.0.1"),
expected: true,
},
{
name: "Localhost range edge",
- setupAddr: iface.WGAddress{
+ setupAddr: wgaddr.Address{
IP: net.ParseIP("192.168.1.1"),
Network: &net.IPNet{
IP: net.ParseIP("192.168.1.0"),
Mask: net.CIDRMask(24, 32),
},
},
- testIP: net.ParseIP("127.255.255.255"),
+ testIP: netip.MustParseAddr("127.255.255.255"),
expected: true,
},
{
name: "Local IP matches",
- setupAddr: iface.WGAddress{
+ setupAddr: wgaddr.Address{
IP: net.ParseIP("192.168.1.1"),
Network: &net.IPNet{
IP: net.ParseIP("192.168.1.0"),
Mask: net.CIDRMask(24, 32),
},
},
- testIP: net.ParseIP("192.168.1.1"),
+ testIP: netip.MustParseAddr("192.168.1.1"),
expected: true,
},
{
name: "Local IP doesn't match",
- setupAddr: iface.WGAddress{
+ setupAddr: wgaddr.Address{
IP: net.ParseIP("192.168.1.1"),
Network: &net.IPNet{
IP: net.ParseIP("192.168.1.0"),
Mask: net.CIDRMask(24, 32),
},
},
- testIP: net.ParseIP("192.168.1.2"),
+ testIP: netip.MustParseAddr("192.168.1.2"),
expected: false,
},
{
name: "IPv6 address",
- setupAddr: iface.WGAddress{
+ setupAddr: wgaddr.Address{
IP: net.ParseIP("fe80::1"),
Network: &net.IPNet{
IP: net.ParseIP("fe80::"),
Mask: net.CIDRMask(64, 128),
},
},
- testIP: net.ParseIP("fe80::1"),
+ testIP: netip.MustParseAddr("fe80::1"),
expected: false,
},
}
@@ -95,7 +96,7 @@ func TestLocalIPManager(t *testing.T) {
manager := newLocalIPManager()
mock := &IFaceMock{
- AddressFunc: func() iface.WGAddress {
+ AddressFunc: func() wgaddr.Address {
return tt.setupAddr
},
}
@@ -174,7 +175,7 @@ func TestLocalIPManager_AllInterfaces(t *testing.T) {
t.Logf("Testing %d IPs", len(tests))
for _, tt := range tests {
t.Run(tt.ip, func(t *testing.T) {
- result := manager.IsLocalIP(net.ParseIP(tt.ip))
+ result := manager.IsLocalIP(netip.MustParseAddr(tt.ip))
require.Equal(t, tt.expected, result, "IP: %s", tt.ip)
})
}
diff --git a/client/firewall/uspfilter/rule.go b/client/firewall/uspfilter/rule.go
index 7a587c832..a23d2011b 100644
--- a/client/firewall/uspfilter/rule.go
+++ b/client/firewall/uspfilter/rule.go
@@ -1,7 +1,6 @@
package uspfilter
import (
- "net"
"net/netip"
"github.com/google/gopacket"
@@ -13,7 +12,7 @@ import (
type PeerRule struct {
id string
mgmtId []byte
- ip net.IP
+ ip netip.Addr
ipLayer gopacket.LayerType
matchByIP bool
protoLayer gopacket.LayerType
diff --git a/client/firewall/uspfilter/tracer.go b/client/firewall/uspfilter/tracer.go
index aff886b58..53350797c 100644
--- a/client/firewall/uspfilter/tracer.go
+++ b/client/firewall/uspfilter/tracer.go
@@ -2,7 +2,7 @@ package uspfilter
import (
"fmt"
- "net"
+ "net/netip"
"time"
"github.com/google/gopacket"
@@ -53,8 +53,8 @@ type TraceResult struct {
}
type PacketTrace struct {
- SourceIP net.IP
- DestinationIP net.IP
+ SourceIP netip.Addr
+ DestinationIP netip.Addr
Protocol string
SourcePort uint16
DestinationPort uint16
@@ -72,8 +72,8 @@ type TCPState struct {
}
type PacketBuilder struct {
- SrcIP net.IP
- DstIP net.IP
+ SrcIP netip.Addr
+ DstIP netip.Addr
Protocol fw.Protocol
SrcPort uint16
DstPort uint16
@@ -126,8 +126,8 @@ func (p *PacketBuilder) buildIPLayer() *layers.IPv4 {
Version: 4,
TTL: 64,
Protocol: layers.IPProtocol(getIPProtocolNumber(p.Protocol)),
- SrcIP: p.SrcIP,
- DstIP: p.DstIP,
+ SrcIP: p.SrcIP.AsSlice(),
+ DstIP: p.DstIP.AsSlice(),
}
}
@@ -260,28 +260,30 @@ func (m *Manager) TracePacket(packetData []byte, direction fw.RuleDirection) *Pa
return m.traceInbound(packetData, trace, d, srcIP, dstIP)
}
-func (m *Manager) traceInbound(packetData []byte, trace *PacketTrace, d *decoder, srcIP net.IP, dstIP net.IP) *PacketTrace {
+func (m *Manager) traceInbound(packetData []byte, trace *PacketTrace, d *decoder, srcIP netip.Addr, dstIP netip.Addr) *PacketTrace {
if m.stateful && m.handleConntrackState(trace, d, srcIP, dstIP) {
return trace
}
- if m.handleLocalDelivery(trace, packetData, d, srcIP, dstIP) {
- return trace
+ if m.localipmanager.IsLocalIP(dstIP) {
+ if m.handleLocalDelivery(trace, packetData, d, srcIP, dstIP) {
+ return trace
+ }
}
if !m.handleRouting(trace) {
return trace
}
- if m.nativeRouter {
+ if m.nativeRouter.Load() {
return m.handleNativeRouter(trace)
}
return m.handleRouteACLs(trace, d, srcIP, dstIP)
}
-func (m *Manager) handleConntrackState(trace *PacketTrace, d *decoder, srcIP, dstIP net.IP) bool {
- allowed := m.isValidTrackedConnection(d, srcIP, dstIP)
+func (m *Manager) handleConntrackState(trace *PacketTrace, d *decoder, srcIP, dstIP netip.Addr) bool {
+ allowed := m.isValidTrackedConnection(d, srcIP, dstIP, 0)
msg := "No existing connection found"
if allowed {
msg = m.buildConntrackStateMessage(d)
@@ -309,39 +311,46 @@ func (m *Manager) buildConntrackStateMessage(d *decoder) string {
return msg
}
-func (m *Manager) handleLocalDelivery(trace *PacketTrace, packetData []byte, d *decoder, srcIP, dstIP net.IP) bool {
- if !m.localForwarding {
- trace.AddResult(StageRouting, "Local forwarding disabled", false)
- trace.AddResult(StageCompleted, "Packet dropped - local forwarding disabled", false)
- return true
- }
-
+func (m *Manager) handleLocalDelivery(trace *PacketTrace, packetData []byte, d *decoder, srcIP, dstIP netip.Addr) bool {
trace.AddResult(StageRouting, "Packet destined for local delivery", true)
ruleId, blocked := m.peerACLsBlock(srcIP, packetData, m.incomingRules, d)
- strRuleId := "implicit"
+ strRuleId := ""
if ruleId != nil {
strRuleId = string(ruleId)
}
-
msg := fmt.Sprintf("Allowed by peer ACL rules (%s)", strRuleId)
if blocked {
msg = fmt.Sprintf("Blocked by peer ACL rules (%s)", strRuleId)
+ trace.AddResult(StagePeerACL, msg, false)
+ trace.AddResult(StageCompleted, "Packet dropped - ACL denied", false)
+ return true
}
- trace.AddResult(StagePeerACL, msg, !blocked)
+ trace.AddResult(StagePeerACL, msg, true)
+ // Handle netstack mode
if m.netstack {
- m.addForwardingResult(trace, "proxy-local", "127.0.0.1", !blocked)
+ switch {
+ case !m.localForwarding:
+ trace.AddResult(StageCompleted, "Packet sent to virtual stack", true)
+ case m.forwarder.Load() != nil:
+ m.addForwardingResult(trace, "proxy-local", "127.0.0.1", true)
+ trace.AddResult(StageCompleted, msgProcessingCompleted, true)
+ default:
+ trace.AddResult(StageCompleted, "Packet dropped - forwarder not initialized", false)
+ }
+ return true
}
- trace.AddResult(StageCompleted, msgProcessingCompleted, !blocked)
+ // In normal mode, packets are allowed through for local delivery
+ trace.AddResult(StageCompleted, msgProcessingCompleted, true)
return true
}
func (m *Manager) handleRouting(trace *PacketTrace) bool {
- if !m.routingEnabled {
+ if !m.routingEnabled.Load() {
trace.AddResult(StageRouting, "Routing disabled", false)
trace.AddResult(StageCompleted, "Packet dropped - routing disabled", false)
return false
@@ -357,14 +366,14 @@ func (m *Manager) handleNativeRouter(trace *PacketTrace) *PacketTrace {
return trace
}
-func (m *Manager) handleRouteACLs(trace *PacketTrace, d *decoder, srcIP, dstIP net.IP) *PacketTrace {
+func (m *Manager) handleRouteACLs(trace *PacketTrace, d *decoder, srcIP, dstIP netip.Addr) *PacketTrace {
proto, _ := getProtocolFromPacket(d)
srcPort, dstPort := getPortsFromPacket(d)
id, allowed := m.routeACLsPass(srcIP, dstIP, proto, srcPort, dstPort)
strId := string(id)
if id == nil {
- strId = "implicit"
+ strId = ""
}
msg := fmt.Sprintf("Allowed by route ACLs (%s)", strId)
@@ -373,7 +382,7 @@ func (m *Manager) handleRouteACLs(trace *PacketTrace, d *decoder, srcIP, dstIP n
}
trace.AddResult(StageRouteACL, msg, allowed)
- if allowed && m.forwarder != nil {
+ if allowed && m.forwarder.Load() != nil {
m.addForwardingResult(trace, "proxy-remote", fmt.Sprintf("%s:%d", dstIP, dstPort), true)
}
@@ -392,7 +401,7 @@ func (m *Manager) addForwardingResult(trace *PacketTrace, action, remoteAddr str
func (m *Manager) traceOutbound(packetData []byte, trace *PacketTrace) *PacketTrace {
// will create or update the connection state
- dropped := m.processOutgoingHooks(packetData)
+ dropped := m.processOutgoingHooks(packetData, 0)
if dropped {
trace.AddResult(StageCompleted, "Packet dropped by outgoing hook", false)
} else {
diff --git a/client/firewall/uspfilter/tracer_test.go b/client/firewall/uspfilter/tracer_test.go
new file mode 100644
index 000000000..48b0ec44d
--- /dev/null
+++ b/client/firewall/uspfilter/tracer_test.go
@@ -0,0 +1,440 @@
+package uspfilter
+
+import (
+ "net"
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ fw "github.com/netbirdio/netbird/client/firewall/manager"
+ "github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
+ "github.com/netbirdio/netbird/client/firewall/uspfilter/forwarder"
+ "github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
+)
+
+func verifyTraceStages(t *testing.T, trace *PacketTrace, expectedStages []PacketStage) {
+ t.Logf("Trace results: %v", trace.Results)
+ actualStages := make([]PacketStage, 0, len(trace.Results))
+ for _, result := range trace.Results {
+ actualStages = append(actualStages, result.Stage)
+ t.Logf("Stage: %s, Message: %s, Allowed: %v", result.Stage, result.Message, result.Allowed)
+ }
+
+ require.ElementsMatch(t, expectedStages, actualStages, "Trace stages don't match expected stages")
+}
+
+func verifyFinalDisposition(t *testing.T, trace *PacketTrace, expectedAllowed bool) {
+ require.NotEmpty(t, trace.Results, "Trace should have results")
+ lastResult := trace.Results[len(trace.Results)-1]
+ require.Equal(t, StageCompleted, lastResult.Stage, "Last stage should be 'Completed'")
+ require.Equal(t, expectedAllowed, lastResult.Allowed, "Final disposition incorrect")
+}
+
+func TestTracePacket(t *testing.T) {
+ setupTracerTest := func(statefulMode bool) *Manager {
+ ifaceMock := &IFaceMock{
+ SetFilterFunc: func(device.PacketFilter) error { return nil },
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
+ IP: net.ParseIP("100.10.0.100"),
+ Network: &net.IPNet{
+ IP: net.ParseIP("100.10.0.0"),
+ Mask: net.CIDRMask(16, 32),
+ },
+ }
+ },
+ }
+
+ m, err := Create(ifaceMock, false, flowLogger)
+ require.NoError(t, err)
+
+ if !statefulMode {
+ m.stateful = false
+ }
+
+ return m
+ }
+
+ createPacketBuilder := func(srcIP, dstIP string, protocol fw.Protocol, srcPort, dstPort uint16, direction fw.RuleDirection) *PacketBuilder {
+ builder := &PacketBuilder{
+ SrcIP: netip.MustParseAddr(srcIP),
+ DstIP: netip.MustParseAddr(dstIP),
+ Protocol: protocol,
+ SrcPort: srcPort,
+ DstPort: dstPort,
+ Direction: direction,
+ }
+
+ if protocol == "tcp" {
+ builder.TCPState = &TCPState{SYN: true}
+ }
+
+ return builder
+ }
+
+ createICMPPacketBuilder := func(srcIP, dstIP string, icmpType, icmpCode uint8, direction fw.RuleDirection) *PacketBuilder {
+ return &PacketBuilder{
+ SrcIP: netip.MustParseAddr(srcIP),
+ DstIP: netip.MustParseAddr(dstIP),
+ Protocol: "icmp",
+ ICMPType: icmpType,
+ ICMPCode: icmpCode,
+ Direction: direction,
+ }
+ }
+
+ testCases := []struct {
+ name string
+ setup func(*Manager)
+ packetBuilder func() *PacketBuilder
+ expectedStages []PacketStage
+ expectedAllow bool
+ }{
+ {
+ name: "LocalTraffic_ACLAllowed",
+ setup: func(m *Manager) {
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolTCP
+ port := &fw.Port{Values: []uint16{80}}
+ action := fw.ActionAccept
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "LocalTraffic_ACLDenied",
+ setup: func(m *Manager) {
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolTCP
+ port := &fw.Port{Values: []uint16{80}}
+ action := fw.ActionDrop
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: false,
+ },
+ {
+ name: "LocalTraffic_WithForwarder",
+ setup: func(m *Manager) {
+ m.netstack = true
+ m.localForwarding = true
+
+ m.forwarder.Store(&forwarder.Forwarder{})
+
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolTCP
+ port := &fw.Port{Values: []uint16{80}}
+ action := fw.ActionAccept
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageForwarding,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "LocalTraffic_WithoutForwarder",
+ setup: func(m *Manager) {
+ m.netstack = true
+ m.localForwarding = false
+
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolTCP
+ port := &fw.Port{Values: []uint16{80}}
+ action := fw.ActionAccept
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "RoutedTraffic_ACLAllowed",
+ setup: func(m *Manager) {
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(false)
+
+ m.forwarder.Store(&forwarder.Forwarder{})
+
+ src := netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), 32)
+ dst := netip.PrefixFrom(netip.AddrFrom4([4]byte{172, 17, 0, 2}), 32)
+ _, err := m.AddRouteFiltering(nil, []netip.Prefix{src}, dst, fw.ProtocolTCP, nil, &fw.Port{Values: []uint16{80}}, fw.ActionAccept)
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "172.17.0.2", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StageRouteACL,
+ StageForwarding,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "RoutedTraffic_ACLDenied",
+ setup: func(m *Manager) {
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(false)
+
+ src := netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), 32)
+ dst := netip.PrefixFrom(netip.AddrFrom4([4]byte{172, 17, 0, 2}), 32)
+ _, err := m.AddRouteFiltering(nil, []netip.Prefix{src}, dst, fw.ProtocolTCP, nil, &fw.Port{Values: []uint16{80}}, fw.ActionDrop)
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "172.17.0.2", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StageRouteACL,
+ StageCompleted,
+ },
+ expectedAllow: false,
+ },
+ {
+ name: "RoutedTraffic_NativeRouter",
+ setup: func(m *Manager) {
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(true)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "172.17.0.2", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StageRouteACL,
+ StageForwarding,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "RoutedTraffic_RoutingDisabled",
+ setup: func(m *Manager) {
+ m.routingEnabled.Store(false)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "172.17.0.2", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StageCompleted,
+ },
+ expectedAllow: false,
+ },
+ {
+ name: "ConnectionTracking_Hit",
+ setup: func(m *Manager) {
+ srcIP := netip.MustParseAddr("100.10.0.100")
+ dstIP := netip.MustParseAddr("1.1.1.1")
+ srcPort := uint16(12345)
+ dstPort := uint16(80)
+
+ m.tcpTracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, conntrack.TCPSyn, 0)
+ },
+ packetBuilder: func() *PacketBuilder {
+ pb := createPacketBuilder("1.1.1.1", "100.10.0.100", "tcp", 80, 12345, fw.RuleDirectionIN)
+ pb.TCPState = &TCPState{SYN: true, ACK: true}
+ return pb
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "OutboundTraffic",
+ setup: func(m *Manager) {
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("100.10.0.100", "1.1.1.1", "tcp", 12345, 80, fw.RuleDirectionOUT)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "ICMPEchoRequest",
+ setup: func(m *Manager) {
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolICMP
+ action := fw.ActionAccept
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, nil, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createICMPPacketBuilder("1.1.1.1", "100.10.0.100", 8, 0, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "ICMPDestinationUnreachable",
+ setup: func(m *Manager) {
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolICMP
+ action := fw.ActionDrop
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, nil, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createICMPPacketBuilder("1.1.1.1", "100.10.0.100", 3, 0, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "UDPTraffic_WithoutHook",
+ setup: func(m *Manager) {
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolUDP
+ port := &fw.Port{Values: []uint16{53}}
+ action := fw.ActionAccept
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "udp", 12345, 53, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: true,
+ },
+ {
+ name: "UDPTraffic_WithHook",
+ setup: func(m *Manager) {
+ hookFunc := func([]byte) bool {
+ return true
+ }
+ m.AddUDPPacketHook(true, netip.MustParseAddr("1.1.1.1"), 53, hookFunc)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "udp", 12345, 53, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageConntrack,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: false,
+ },
+ {
+ name: "StatefulDisabled_NoTracking",
+ setup: func(m *Manager) {
+ m.stateful = false
+
+ ip := net.ParseIP("1.1.1.1")
+ proto := fw.ProtocolTCP
+ port := &fw.Port{Values: []uint16{80}}
+ action := fw.ActionDrop
+ _, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ require.NoError(t, err)
+ },
+ packetBuilder: func() *PacketBuilder {
+ return createPacketBuilder("1.1.1.1", "100.10.0.100", "tcp", 12345, 80, fw.RuleDirectionIN)
+ },
+ expectedStages: []PacketStage{
+ StageReceived,
+ StageRouting,
+ StagePeerACL,
+ StageCompleted,
+ },
+ expectedAllow: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ m := setupTracerTest(true)
+
+ tc.setup(m)
+
+ require.True(t, m.localipmanager.IsLocalIP(netip.MustParseAddr("100.10.0.100")),
+ "100.10.0.100 should be recognized as a local IP")
+ require.False(t, m.localipmanager.IsLocalIP(netip.MustParseAddr("172.17.0.2")),
+ "172.17.0.2 should not be recognized as a local IP")
+
+ pb := tc.packetBuilder()
+
+ trace, err := m.TracePacketFromBuilder(pb)
+ require.NoError(t, err)
+
+ verifyTraceStages(t, trace, tc.expectedStages)
+ verifyFinalDisposition(t, trace, tc.expectedAllow)
+ })
+ }
+}
diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/uspfilter.go
index 01a3976b4..723ef6299 100644
--- a/client/firewall/uspfilter/uspfilter.go
+++ b/client/firewall/uspfilter/uspfilter.go
@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
@@ -66,9 +67,9 @@ func (r RouteRules) Sort() {
// Manager userspace firewall manager
type Manager struct {
// outgoingRules is used for hooks only
- outgoingRules map[string]RuleSet
+ outgoingRules map[netip.Addr]RuleSet
// incomingRules is used for filtering and hooks
- incomingRules map[string]RuleSet
+ incomingRules map[netip.Addr]RuleSet
routeRules RouteRules
wgNetwork *net.IPNet
decoders sync.Pool
@@ -80,9 +81,9 @@ type Manager struct {
// indicates whether server routes are disabled
disableServerRoutes bool
// indicates whether we forward packets not destined for ourselves
- routingEnabled bool
+ routingEnabled atomic.Bool
// indicates whether we leave forwarding and filtering to the native firewall
- nativeRouter bool
+ nativeRouter atomic.Bool
// indicates whether we track outbound connections
stateful bool
// indicates whether wireguards runs in netstack mode
@@ -95,7 +96,7 @@ type Manager struct {
udpTracker *conntrack.UDPTracker
icmpTracker *conntrack.ICMPTracker
tcpTracker *conntrack.TCPTracker
- forwarder *forwarder.Forwarder
+ forwarder atomic.Pointer[forwarder.Forwarder]
logger *nblog.Logger
flowLogger nftypes.FlowLogger
}
@@ -168,18 +169,18 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
},
},
nativeFirewall: nativeFirewall,
- outgoingRules: make(map[string]RuleSet),
- incomingRules: make(map[string]RuleSet),
+ outgoingRules: make(map[netip.Addr]RuleSet),
+ incomingRules: make(map[netip.Addr]RuleSet),
wgIface: iface,
localipmanager: newLocalIPManager(),
disableServerRoutes: disableServerRoutes,
- routingEnabled: false,
stateful: !disableConntrack,
logger: nblog.NewFromLogrus(log.StandardLogger()),
flowLogger: flowLogger,
netstack: netstack.IsEnabled(),
localForwarding: enableLocalForwarding,
}
+ m.routingEnabled.Store(false)
if err := m.localipmanager.UpdateLocalIPs(iface); err != nil {
return nil, fmt.Errorf("update local IPs: %w", err)
@@ -211,7 +212,7 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
}
func (m *Manager) blockInvalidRouted(iface common.IFaceMapper) error {
- if m.forwarder == nil {
+ if m.forwarder.Load() == nil {
return nil
}
wgPrefix, err := netip.ParsePrefix(iface.Address().Network.String())
@@ -255,20 +256,20 @@ func (m *Manager) determineRouting() error {
switch {
case disableUspRouting:
- m.routingEnabled = false
- m.nativeRouter = false
+ m.routingEnabled.Store(false)
+ m.nativeRouter.Store(false)
log.Info("userspace routing is disabled")
case m.disableServerRoutes:
// if server routes are disabled we will let packets pass to the native stack
- m.routingEnabled = true
- m.nativeRouter = true
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(true)
log.Info("server routes are disabled")
case forceUserspaceRouter:
- m.routingEnabled = true
- m.nativeRouter = false
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(false)
log.Info("userspace routing is forced")
@@ -276,19 +277,19 @@ func (m *Manager) determineRouting() error {
// if the OS supports routing natively, then we don't need to filter/route ourselves
// netstack mode won't support native routing as there is no interface
- m.routingEnabled = true
- m.nativeRouter = true
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(true)
log.Info("native routing is enabled")
default:
- m.routingEnabled = true
- m.nativeRouter = false
+ m.routingEnabled.Store(true)
+ m.nativeRouter.Store(false)
log.Info("userspace routing enabled by default")
}
- if m.routingEnabled && !m.nativeRouter {
+ if m.routingEnabled.Load() && !m.nativeRouter.Load() {
return m.initForwarder()
}
@@ -297,24 +298,24 @@ func (m *Manager) determineRouting() error {
// initForwarder initializes the forwarder, it disables routing on errors
func (m *Manager) initForwarder() error {
- if m.forwarder != nil {
+ if m.forwarder.Load() != nil {
return nil
}
// Only supported in userspace mode as we need to inject packets back into wireguard directly
intf := m.wgIface.GetWGDevice()
if intf == nil {
- m.routingEnabled = false
+ m.routingEnabled.Store(false)
return errors.New("forwarding not supported")
}
forwarder, err := forwarder.New(m.wgIface, m.logger, m.flowLogger, m.netstack)
if err != nil {
- m.routingEnabled = false
+ m.routingEnabled.Store(false)
return fmt.Errorf("create forwarder: %w", err)
}
- m.forwarder = forwarder
+ m.forwarder.Store(forwarder)
log.Debug("forwarder initialized")
@@ -330,7 +331,7 @@ func (m *Manager) IsServerRouteSupported() bool {
}
func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
- if m.nativeRouter && m.nativeFirewall != nil {
+ if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.AddNatRule(pair)
}
@@ -341,7 +342,7 @@ func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
// RemoveNatRule removes a routing firewall rule
func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
- if m.nativeRouter && m.nativeFirewall != nil {
+ if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.RemoveNatRule(pair)
}
return nil
@@ -360,17 +361,23 @@ func (m *Manager) AddPeerFiltering(
action firewall.Action,
_ string,
) ([]firewall.Rule, error) {
+ // TODO: fix in upper layers
+ i, ok := netip.AddrFromSlice(ip)
+ if !ok {
+ return nil, fmt.Errorf("invalid IP: %s", ip)
+ }
+
+ i = i.Unmap()
r := PeerRule{
id: uuid.New().String(),
mgmtId: id,
- ip: ip,
+ ip: i,
ipLayer: layers.LayerTypeIPv6,
matchByIP: true,
drop: action == firewall.ActionDrop,
}
- if ipNormalized := ip.To4(); ipNormalized != nil {
+ if i.Is4() {
r.ipLayer = layers.LayerTypeIPv4
- r.ip = ipNormalized
}
if s := r.ip.String(); s == "0.0.0.0" || s == "::" {
@@ -395,10 +402,10 @@ func (m *Manager) AddPeerFiltering(
}
m.mutex.Lock()
- if _, ok := m.incomingRules[r.ip.String()]; !ok {
- m.incomingRules[r.ip.String()] = make(RuleSet)
+ if _, ok := m.incomingRules[r.ip]; !ok {
+ m.incomingRules[r.ip] = make(RuleSet)
}
- m.incomingRules[r.ip.String()][r.id] = r
+ m.incomingRules[r.ip][r.id] = r
m.mutex.Unlock()
return []firewall.Rule{&r}, nil
}
@@ -412,13 +419,10 @@ func (m *Manager) AddRouteFiltering(
dPort *firewall.Port,
action firewall.Action,
) (firewall.Rule, error) {
- if m.nativeRouter && m.nativeFirewall != nil {
+ if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.AddRouteFiltering(id, sources, destination, proto, sPort, dPort, action)
}
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
ruleID := uuid.New().String()
rule := RouteRule{
// TODO: consolidate these IDs
@@ -432,14 +436,16 @@ func (m *Manager) AddRouteFiltering(
action: action,
}
+ m.mutex.Lock()
m.routeRules = append(m.routeRules, rule)
m.routeRules.Sort()
+ m.mutex.Unlock()
return &rule, nil
}
func (m *Manager) DeleteRouteRule(rule firewall.Rule) error {
- if m.nativeRouter && m.nativeFirewall != nil {
+ if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.DeleteRouteRule(rule)
}
@@ -468,10 +474,10 @@ func (m *Manager) DeletePeerRule(rule firewall.Rule) error {
return fmt.Errorf("delete rule: invalid rule type: %T", rule)
}
- if _, ok := m.incomingRules[r.ip.String()][r.id]; !ok {
+ if _, ok := m.incomingRules[r.ip][r.id]; !ok {
return fmt.Errorf("delete rule: no rule with such id: %v", r.id)
}
- delete(m.incomingRules[r.ip.String()], r.id)
+ delete(m.incomingRules[r.ip], r.id)
return nil
}
@@ -504,13 +510,13 @@ func (m *Manager) DeleteDNATRule(rule firewall.Rule) error {
}
// DropOutgoing filter outgoing packets
-func (m *Manager) DropOutgoing(packetData []byte) bool {
- return m.processOutgoingHooks(packetData)
+func (m *Manager) DropOutgoing(packetData []byte, size int) bool {
+ return m.processOutgoingHooks(packetData, size)
}
// DropIncoming filter incoming packets
-func (m *Manager) DropIncoming(packetData []byte) bool {
- return m.dropFilter(packetData)
+func (m *Manager) DropIncoming(packetData []byte, size int) bool {
+ return m.dropFilter(packetData, size)
}
// UpdateLocalIPs updates the list of local IPs
@@ -518,10 +524,7 @@ func (m *Manager) UpdateLocalIPs() error {
return m.localipmanager.UpdateLocalIPs(m.wgIface)
}
-func (m *Manager) processOutgoingHooks(packetData []byte) bool {
- m.mutex.RLock()
- defer m.mutex.RUnlock()
-
+func (m *Manager) processOutgoingHooks(packetData []byte, size int) bool {
d := m.decoders.Get().(*decoder)
defer m.decoders.Put(d)
@@ -534,31 +537,34 @@ func (m *Manager) processOutgoingHooks(packetData []byte) bool {
}
srcIP, dstIP := m.extractIPs(d)
- if srcIP == nil {
+ if !srcIP.IsValid() {
+ m.logger.Error("Unknown network layer: %v", d.decoded[0])
return false
}
- // Track all protocols if stateful mode is enabled
- if m.stateful {
- m.trackOutbound(d, srcIP, dstIP)
+ if d.decoded[1] == layers.LayerTypeUDP && m.udpHooksDrop(uint16(d.udp.DstPort), dstIP, packetData) {
+ return true
}
- // Process UDP hooks even if stateful mode is disabled
- if d.decoded[1] == layers.LayerTypeUDP {
- return m.checkUDPHooks(d, dstIP, packetData)
+ if m.stateful {
+ m.trackOutbound(d, srcIP, dstIP, size)
}
return false
}
-func (m *Manager) extractIPs(d *decoder) (srcIP, dstIP net.IP) {
+func (m *Manager) extractIPs(d *decoder) (srcIP, dstIP netip.Addr) {
switch d.decoded[0] {
case layers.LayerTypeIPv4:
- return d.ip4.SrcIP, d.ip4.DstIP
+ src, _ := netip.AddrFromSlice(d.ip4.SrcIP)
+ dst, _ := netip.AddrFromSlice(d.ip4.DstIP)
+ return src, dst
case layers.LayerTypeIPv6:
- return d.ip6.SrcIP, d.ip6.DstIP
+ src, _ := netip.AddrFromSlice(d.ip6.SrcIP)
+ dst, _ := netip.AddrFromSlice(d.ip6.DstIP)
+ return src, dst
default:
- return nil, nil
+ return netip.Addr{}, netip.Addr{}
}
}
@@ -585,51 +591,70 @@ func getTCPFlags(tcp *layers.TCP) uint8 {
return flags
}
-func (m *Manager) trackOutbound(d *decoder, srcIP, dstIP net.IP) {
+func (m *Manager) trackOutbound(d *decoder, srcIP, dstIP netip.Addr, size int) {
transport := d.decoded[1]
switch transport {
case layers.LayerTypeUDP:
- m.udpTracker.TrackOutbound(srcIP, dstIP, uint16(d.udp.SrcPort), uint16(d.udp.DstPort))
+ m.udpTracker.TrackOutbound(srcIP, dstIP, uint16(d.udp.SrcPort), uint16(d.udp.DstPort), size)
case layers.LayerTypeTCP:
flags := getTCPFlags(&d.tcp)
- m.tcpTracker.TrackOutbound(srcIP, dstIP, uint16(d.tcp.SrcPort), uint16(d.tcp.DstPort), flags)
+ m.tcpTracker.TrackOutbound(srcIP, dstIP, uint16(d.tcp.SrcPort), uint16(d.tcp.DstPort), flags, size)
case layers.LayerTypeICMPv4:
- m.icmpTracker.TrackOutbound(srcIP, dstIP, d.icmp4.Id, d.icmp4.Seq, d.icmp4.TypeCode)
+ m.icmpTracker.TrackOutbound(srcIP, dstIP, d.icmp4.Id, d.icmp4.TypeCode, size)
}
}
-func (m *Manager) trackInbound(d *decoder, srcIP, dstIP net.IP) {
+func (m *Manager) trackInbound(d *decoder, srcIP, dstIP netip.Addr, ruleID []byte, size int) {
transport := d.decoded[1]
switch transport {
case layers.LayerTypeUDP:
- m.udpTracker.TrackInbound(srcIP, dstIP, uint16(d.udp.SrcPort), uint16(d.udp.DstPort))
+ m.udpTracker.TrackInbound(srcIP, dstIP, uint16(d.udp.SrcPort), uint16(d.udp.DstPort), ruleID, size)
case layers.LayerTypeTCP:
flags := getTCPFlags(&d.tcp)
- m.tcpTracker.TrackInbound(srcIP, dstIP, uint16(d.tcp.SrcPort), uint16(d.tcp.DstPort), flags)
+ m.tcpTracker.TrackInbound(srcIP, dstIP, uint16(d.tcp.SrcPort), uint16(d.tcp.DstPort), flags, ruleID, size)
case layers.LayerTypeICMPv4:
- m.icmpTracker.TrackInbound(srcIP, dstIP, d.icmp4.Id, d.icmp4.Seq, d.icmp4.TypeCode)
+ m.icmpTracker.TrackInbound(srcIP, dstIP, d.icmp4.Id, d.icmp4.TypeCode, ruleID, size)
}
}
-func (m *Manager) checkUDPHooks(d *decoder, dstIP net.IP, packetData []byte) bool {
- for _, ipKey := range []string{dstIP.String(), "0.0.0.0", "::"} {
- if rules, exists := m.outgoingRules[ipKey]; exists {
- for _, rule := range rules {
- if rule.udpHook != nil && portsMatch(rule.dPort, uint16(d.udp.DstPort)) {
- return rule.udpHook(packetData)
- }
+// udpHooksDrop checks if any UDP hooks should drop the packet
+func (m *Manager) udpHooksDrop(dport uint16, dstIP netip.Addr, packetData []byte) bool {
+ m.mutex.RLock()
+ defer m.mutex.RUnlock()
+
+ // Check specific destination IP first
+ if rules, exists := m.outgoingRules[dstIP]; exists {
+ for _, rule := range rules {
+ if rule.udpHook != nil && portsMatch(rule.dPort, dport) {
+ return rule.udpHook(packetData)
}
}
}
+
+ // Check IPv4 unspecified address
+ if rules, exists := m.outgoingRules[netip.IPv4Unspecified()]; exists {
+ for _, rule := range rules {
+ if rule.udpHook != nil && portsMatch(rule.dPort, dport) {
+ return rule.udpHook(packetData)
+ }
+ }
+ }
+
+ // Check IPv6 unspecified address
+ if rules, exists := m.outgoingRules[netip.IPv6Unspecified()]; exists {
+ for _, rule := range rules {
+ if rule.udpHook != nil && portsMatch(rule.dPort, dport) {
+ return rule.udpHook(packetData)
+ }
+ }
+ }
+
return false
}
// dropFilter implements filtering logic for incoming packets.
// If it returns true, the packet should be dropped.
-func (m *Manager) dropFilter(packetData []byte) bool {
- m.mutex.RLock()
- defer m.mutex.RUnlock()
-
+func (m *Manager) dropFilter(packetData []byte, size int) bool {
d := m.decoders.Get().(*decoder)
defer m.decoders.Put(d)
@@ -638,19 +663,19 @@ func (m *Manager) dropFilter(packetData []byte) bool {
}
srcIP, dstIP := m.extractIPs(d)
- if srcIP == nil {
+ if !srcIP.IsValid() {
m.logger.Error("Unknown network layer: %v", d.decoded[0])
return true
}
// For all inbound traffic, first check if it matches a tracked connection.
// This must happen before any other filtering because the packets are statefully tracked.
- if m.stateful && m.isValidTrackedConnection(d, srcIP, dstIP) {
+ if m.stateful && m.isValidTrackedConnection(d, srcIP, dstIP, size) {
return false
}
if m.localipmanager.IsLocalIP(dstIP) {
- return m.handleLocalTraffic(d, srcIP, dstIP, packetData)
+ return m.handleLocalTraffic(d, srcIP, dstIP, packetData, size)
}
return m.handleRoutedTraffic(d, srcIP, dstIP, packetData)
@@ -658,27 +683,28 @@ func (m *Manager) dropFilter(packetData []byte) bool {
// handleLocalTraffic handles local traffic.
// If it returns true, the packet should be dropped.
-func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP net.IP, packetData []byte) bool {
- if ruleId, blocked := m.peerACLsBlock(srcIP, packetData, m.incomingRules, d); blocked {
- srcAddr, _ := netip.AddrFromSlice(srcIP)
- dstAddr, _ := netip.AddrFromSlice(dstIP)
+func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP netip.Addr, packetData []byte, size int) bool {
+ ruleID, blocked := m.peerACLsBlock(srcIP, packetData, m.incomingRules, d)
+ if blocked {
_, pnum := getProtocolFromPacket(d)
srcPort, dstPort := getPortsFromPacket(d)
m.logger.Trace("Dropping local packet (ACL denied): rule_id=%s proto=%v src=%s:%d dst=%s:%d",
- ruleId, pnum, srcAddr, srcPort, dstAddr, dstPort)
+ ruleID, pnum, srcIP, srcPort, dstIP, dstPort)
m.flowLogger.StoreEvent(nftypes.EventFields{
FlowID: uuid.New(),
Type: nftypes.TypeDrop,
- RuleID: ruleId,
+ RuleID: ruleID,
Direction: nftypes.Ingress,
Protocol: pnum,
- SourceIP: srcAddr,
- DestIP: dstAddr,
+ SourceIP: srcIP,
+ DestIP: dstIP,
SourcePort: srcPort,
DestPort: dstPort,
// TODO: icmp type/code
+ RxPackets: 1,
+ RxBytes: uint64(size),
})
return true
}
@@ -689,7 +715,7 @@ func (m *Manager) handleLocalTraffic(d *decoder, srcIP, dstIP net.IP, packetData
}
// track inbound packets to get the correct direction and session id for flows
- m.trackInbound(d, srcIP, dstIP)
+ m.trackInbound(d, srcIP, dstIP, ruleID, size)
return false
}
@@ -700,12 +726,12 @@ func (m *Manager) handleNetstackLocalTraffic(packetData []byte) bool {
return false
}
- if m.forwarder == nil {
+ if m.forwarder.Load() == nil {
m.logger.Trace("Dropping local packet (forwarder not initialized)")
return true
}
- if err := m.forwarder.InjectIncomingPacket(packetData); err != nil {
+ if err := m.forwarder.Load().InjectIncomingPacket(packetData); err != nil {
m.logger.Error("Failed to inject local packet: %v", err)
}
@@ -715,37 +741,34 @@ func (m *Manager) handleNetstackLocalTraffic(packetData []byte) bool {
// handleRoutedTraffic handles routed traffic.
// If it returns true, the packet should be dropped.
-func (m *Manager) handleRoutedTraffic(d *decoder, srcIP, dstIP net.IP, packetData []byte) bool {
+func (m *Manager) handleRoutedTraffic(d *decoder, srcIP, dstIP netip.Addr, packetData []byte) bool {
// Drop if routing is disabled
- if !m.routingEnabled {
+ if !m.routingEnabled.Load() {
m.logger.Trace("Dropping routed packet (routing disabled): src=%s dst=%s",
srcIP, dstIP)
return true
}
// Pass to native stack if native router is enabled or forced
- if m.nativeRouter {
+ if m.nativeRouter.Load() {
return false
}
proto, pnum := getProtocolFromPacket(d)
srcPort, dstPort := getPortsFromPacket(d)
- if id, pass := m.routeACLsPass(srcIP, dstIP, proto, srcPort, dstPort); !pass {
- srcAddr, _ := netip.AddrFromSlice(srcIP)
- dstAddr, _ := netip.AddrFromSlice(dstIP)
-
+ if ruleID, pass := m.routeACLsPass(srcIP, dstIP, proto, srcPort, dstPort); !pass {
m.logger.Trace("Dropping routed packet (ACL denied): rule_id=%s proto=%v src=%s:%d dst=%s:%d",
- id, pnum, srcIP, srcPort, dstIP, dstPort)
+ ruleID, pnum, srcIP, srcPort, dstIP, dstPort)
m.flowLogger.StoreEvent(nftypes.EventFields{
FlowID: uuid.New(),
Type: nftypes.TypeDrop,
- RuleID: id,
+ RuleID: ruleID,
Direction: nftypes.Ingress,
Protocol: pnum,
- SourceIP: srcAddr,
- DestIP: dstAddr,
+ SourceIP: srcIP,
+ DestIP: dstIP,
SourcePort: srcPort,
DestPort: dstPort,
// TODO: icmp type/code
@@ -754,7 +777,7 @@ func (m *Manager) handleRoutedTraffic(d *decoder, srcIP, dstIP net.IP, packetDat
}
// Let forwarder handle the packet if it passed route ACLs
- if err := m.forwarder.InjectIncomingPacket(packetData); err != nil {
+ if err := m.forwarder.Load().InjectIncomingPacket(packetData); err != nil {
m.logger.Error("Failed to inject incoming packet: %v", err)
}
@@ -799,7 +822,7 @@ func (m *Manager) isValidPacket(d *decoder, packetData []byte) bool {
return true
}
-func (m *Manager) isValidTrackedConnection(d *decoder, srcIP, dstIP net.IP) bool {
+func (m *Manager) isValidTrackedConnection(d *decoder, srcIP, dstIP netip.Addr, size int) bool {
switch d.decoded[1] {
case layers.LayerTypeTCP:
return m.tcpTracker.IsValidInbound(
@@ -808,6 +831,7 @@ func (m *Manager) isValidTrackedConnection(d *decoder, srcIP, dstIP net.IP) bool
uint16(d.tcp.SrcPort),
uint16(d.tcp.DstPort),
getTCPFlags(&d.tcp),
+ size,
)
case layers.LayerTypeUDP:
@@ -816,6 +840,7 @@ func (m *Manager) isValidTrackedConnection(d *decoder, srcIP, dstIP net.IP) bool
dstIP,
uint16(d.udp.SrcPort),
uint16(d.udp.DstPort),
+ size,
)
case layers.LayerTypeICMPv4:
@@ -823,8 +848,8 @@ func (m *Manager) isValidTrackedConnection(d *decoder, srcIP, dstIP net.IP) bool
srcIP,
dstIP,
d.icmp4.Id,
- d.icmp4.Seq,
d.icmp4.TypeCode.Type(),
+ size,
)
// TODO: ICMPv6
@@ -844,20 +869,22 @@ func (m *Manager) isSpecialICMP(d *decoder) bool {
icmpType == layers.ICMPv4TypeTimeExceeded
}
-func (m *Manager) peerACLsBlock(srcIP net.IP, packetData []byte, rules map[string]RuleSet, d *decoder) ([]byte, bool) {
+func (m *Manager) peerACLsBlock(srcIP netip.Addr, packetData []byte, rules map[netip.Addr]RuleSet, d *decoder) ([]byte, bool) {
+ m.mutex.RLock()
+ defer m.mutex.RUnlock()
if m.isSpecialICMP(d) {
return nil, false
}
- if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[srcIP.String()], d); ok {
+ if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[srcIP], d); ok {
return mgmtId, filter
}
- if mgmtId, filter, ok := validateRule(srcIP, packetData, rules["0.0.0.0"], d); ok {
+ if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[netip.IPv4Unspecified()], d); ok {
return mgmtId, filter
}
- if mgmtId, filter, ok := validateRule(srcIP, packetData, rules["::"], d); ok {
+ if mgmtId, filter, ok := validateRule(srcIP, packetData, rules[netip.IPv6Unspecified()], d); ok {
return mgmtId, filter
}
@@ -882,10 +909,10 @@ func portsMatch(rulePort *firewall.Port, packetPort uint16) bool {
return false
}
-func validateRule(ip net.IP, packetData []byte, rules map[string]PeerRule, d *decoder) ([]byte, bool, bool) {
+func validateRule(ip netip.Addr, packetData []byte, rules map[string]PeerRule, d *decoder) ([]byte, bool, bool) {
payloadLayer := d.decoded[1]
for _, rule := range rules {
- if rule.matchByIP && !ip.Equal(rule.ip) {
+ if rule.matchByIP && ip.Compare(rule.ip) != 0 {
continue
}
@@ -919,16 +946,13 @@ func validateRule(ip net.IP, packetData []byte, rules map[string]PeerRule, d *de
return nil, false, false
}
-// routeACLsPass returns treu if the packet is allowed by the route ACLs
-func (m *Manager) routeACLsPass(srcIP, dstIP net.IP, proto firewall.Protocol, srcPort, dstPort uint16) ([]byte, bool) {
+// routeACLsPass returns true if the packet is allowed by the route ACLs
+func (m *Manager) routeACLsPass(srcIP, dstIP netip.Addr, proto firewall.Protocol, srcPort, dstPort uint16) ([]byte, bool) {
m.mutex.RLock()
defer m.mutex.RUnlock()
- srcAddr := netip.AddrFrom4([4]byte(srcIP.To4()))
- dstAddr := netip.AddrFrom4([4]byte(dstIP.To4()))
-
for _, rule := range m.routeRules {
- if m.ruleMatches(rule, srcAddr, dstAddr, proto, srcPort, dstPort) {
+ if matches := m.ruleMatches(rule, srcIP, dstIP, proto, srcPort, dstPort); matches {
return rule.mgmtId, rule.action == firewall.ActionAccept
}
}
@@ -972,9 +996,7 @@ func (m *Manager) SetNetwork(network *net.IPNet) {
// AddUDPPacketHook calls hook when UDP packet from given direction matched
//
// Hook function returns flag which indicates should be the matched package dropped or not
-func (m *Manager) AddUDPPacketHook(
- in bool, ip net.IP, dPort uint16, hook func([]byte) bool,
-) string {
+func (m *Manager) AddUDPPacketHook(in bool, ip netip.Addr, dPort uint16, hook func(packet []byte) bool) string {
r := PeerRule{
id: uuid.New().String(),
ip: ip,
@@ -984,23 +1006,22 @@ func (m *Manager) AddUDPPacketHook(
udpHook: hook,
}
- if ip.To4() != nil {
+ if ip.Is4() {
r.ipLayer = layers.LayerTypeIPv4
}
m.mutex.Lock()
if in {
- if _, ok := m.incomingRules[r.ip.String()]; !ok {
- m.incomingRules[r.ip.String()] = make(map[string]PeerRule)
+ if _, ok := m.incomingRules[r.ip]; !ok {
+ m.incomingRules[r.ip] = make(map[string]PeerRule)
}
- m.incomingRules[r.ip.String()][r.id] = r
+ m.incomingRules[r.ip][r.id] = r
} else {
- if _, ok := m.outgoingRules[r.ip.String()]; !ok {
- m.outgoingRules[r.ip.String()] = make(map[string]PeerRule)
+ if _, ok := m.outgoingRules[r.ip]; !ok {
+ m.outgoingRules[r.ip] = make(map[string]PeerRule)
}
- m.outgoingRules[r.ip.String()][r.id] = r
+ m.outgoingRules[r.ip][r.id] = r
}
-
m.mutex.Unlock()
return r.id
@@ -1048,20 +1069,21 @@ func (m *Manager) DisableRouting() error {
m.mutex.Lock()
defer m.mutex.Unlock()
- if m.forwarder == nil {
+ fwder := m.forwarder.Load()
+ if fwder == nil {
return nil
}
- m.routingEnabled = false
- m.nativeRouter = false
+ m.routingEnabled.Store(false)
+ m.nativeRouter.Store(false)
// don't stop forwarder if in use by netstack
if m.netstack && m.localForwarding {
return nil
}
- m.forwarder.Stop()
- m.forwarder = nil
+ fwder.Stop()
+ m.forwarder.Store(nil)
log.Debug("forwarder stopped")
diff --git a/client/firewall/uspfilter/uspfilter_bench_test.go b/client/firewall/uspfilter/uspfilter_bench_test.go
index ea9a4285a..beb5b9336 100644
--- a/client/firewall/uspfilter/uspfilter_bench_test.go
+++ b/client/firewall/uspfilter/uspfilter_bench_test.go
@@ -171,7 +171,7 @@ func BenchmarkCoreFiltering(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
defer b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.wgNetwork = &net.IPNet{
@@ -193,13 +193,13 @@ func BenchmarkCoreFiltering(b *testing.B) {
// For stateful scenarios, establish the connection
if sc.stateful {
- manager.processOutgoingHooks(outbound)
+ manager.processOutgoingHooks(outbound, 0)
}
// Measure inbound packet processing
b.ResetTimer()
for i := 0; i < b.N; i++ {
- manager.dropFilter(inbound)
+ manager.dropFilter(inbound, 0)
}
})
}
@@ -216,7 +216,7 @@ func BenchmarkStateScaling(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.wgNetwork = &net.IPNet{
@@ -230,7 +230,7 @@ func BenchmarkStateScaling(b *testing.B) {
for i := 0; i < count; i++ {
outbound := generatePacket(b, srcIPs[i], dstIPs[i],
uint16(1024+i), 80, layers.IPProtocolTCP)
- manager.processOutgoingHooks(outbound)
+ manager.processOutgoingHooks(outbound, 0)
}
// Test packet
@@ -238,11 +238,11 @@ func BenchmarkStateScaling(b *testing.B) {
testIn := generatePacket(b, dstIPs[0], srcIPs[0], 80, 1024, layers.IPProtocolTCP)
// First establish our test connection
- manager.processOutgoingHooks(testOut)
+ manager.processOutgoingHooks(testOut, 0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
- manager.dropFilter(testIn)
+ manager.dropFilter(testIn, 0)
}
})
}
@@ -264,7 +264,7 @@ func BenchmarkEstablishmentOverhead(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.wgNetwork = &net.IPNet{
@@ -278,12 +278,12 @@ func BenchmarkEstablishmentOverhead(b *testing.B) {
inbound := generatePacket(b, dstIP, srcIP, 80, 1024, layers.IPProtocolTCP)
if sc.established {
- manager.processOutgoingHooks(outbound)
+ manager.processOutgoingHooks(outbound, 0)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
- manager.dropFilter(inbound)
+ manager.dropFilter(inbound, 0)
}
})
}
@@ -463,7 +463,7 @@ func BenchmarkRoutedNetworkReturn(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
// Setup scenario
@@ -477,25 +477,25 @@ func BenchmarkRoutedNetworkReturn(b *testing.B) {
// For stateful cases and established connections
if !strings.Contains(sc.name, "allow_non_wg") ||
(strings.Contains(sc.state, "established") || sc.state == "post_handshake") {
- manager.processOutgoingHooks(outbound)
+ manager.processOutgoingHooks(outbound, 0)
// For TCP post-handshake, simulate full handshake
if sc.state == "post_handshake" {
// SYN
syn := generateTCPPacketWithFlags(b, srcIP, dstIP, 1024, 80, uint16(conntrack.TCPSyn))
- manager.processOutgoingHooks(syn)
+ manager.processOutgoingHooks(syn, 0)
// SYN-ACK
synack := generateTCPPacketWithFlags(b, dstIP, srcIP, 80, 1024, uint16(conntrack.TCPSyn|conntrack.TCPAck))
- manager.dropFilter(synack)
+ manager.dropFilter(synack, 0)
// ACK
ack := generateTCPPacketWithFlags(b, srcIP, dstIP, 1024, 80, uint16(conntrack.TCPAck))
- manager.processOutgoingHooks(ack)
+ manager.processOutgoingHooks(ack, 0)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
- manager.dropFilter(inbound)
+ manager.dropFilter(inbound, 0)
}
})
}
@@ -590,7 +590,7 @@ func BenchmarkLongLivedConnections(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
defer b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.SetNetwork(&net.IPNet{
@@ -624,17 +624,17 @@ func BenchmarkLongLivedConnections(b *testing.B) {
// Initial SYN
syn := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
uint16(1024+i), 80, uint16(conntrack.TCPSyn))
- manager.processOutgoingHooks(syn)
+ manager.processOutgoingHooks(syn, 0)
// SYN-ACK
synack := generateTCPPacketWithFlags(b, dstIPs[i], srcIPs[i],
80, uint16(1024+i), uint16(conntrack.TCPSyn|conntrack.TCPAck))
- manager.dropFilter(synack)
+ manager.dropFilter(synack, 0)
// ACK
ack := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
uint16(1024+i), 80, uint16(conntrack.TCPAck))
- manager.processOutgoingHooks(ack)
+ manager.processOutgoingHooks(ack, 0)
}
// Prepare test packets simulating bidirectional traffic
@@ -655,9 +655,9 @@ func BenchmarkLongLivedConnections(b *testing.B) {
// Simulate bidirectional traffic
// First outbound data
- manager.processOutgoingHooks(outPackets[connIdx])
+ manager.processOutgoingHooks(outPackets[connIdx], 0)
// Then inbound response - this is what we're actually measuring
- manager.dropFilter(inPackets[connIdx])
+ manager.dropFilter(inPackets[connIdx], 0)
}
})
}
@@ -678,7 +678,7 @@ func BenchmarkShortLivedConnections(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
defer b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.SetNetwork(&net.IPNet{
@@ -761,19 +761,19 @@ func BenchmarkShortLivedConnections(b *testing.B) {
p := patterns[connIdx]
// Connection establishment
- manager.processOutgoingHooks(p.syn)
- manager.dropFilter(p.synAck)
- manager.processOutgoingHooks(p.ack)
+ manager.processOutgoingHooks(p.syn, 0)
+ manager.dropFilter(p.synAck, 0)
+ manager.processOutgoingHooks(p.ack, 0)
// Data transfer
- manager.processOutgoingHooks(p.request)
- manager.dropFilter(p.response)
+ manager.processOutgoingHooks(p.request, 0)
+ manager.dropFilter(p.response, 0)
// Connection teardown
- manager.processOutgoingHooks(p.finClient)
- manager.dropFilter(p.ackServer)
- manager.dropFilter(p.finServer)
- manager.processOutgoingHooks(p.ackClient)
+ manager.processOutgoingHooks(p.finClient, 0)
+ manager.dropFilter(p.ackServer, 0)
+ manager.dropFilter(p.finServer, 0)
+ manager.processOutgoingHooks(p.ackClient, 0)
}
})
}
@@ -794,7 +794,7 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
defer b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.SetNetwork(&net.IPNet{
@@ -826,15 +826,15 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
for i := 0; i < sc.connCount; i++ {
syn := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
uint16(1024+i), 80, uint16(conntrack.TCPSyn))
- manager.processOutgoingHooks(syn)
+ manager.processOutgoingHooks(syn, 0)
synack := generateTCPPacketWithFlags(b, dstIPs[i], srcIPs[i],
80, uint16(1024+i), uint16(conntrack.TCPSyn|conntrack.TCPAck))
- manager.dropFilter(synack)
+ manager.dropFilter(synack, 0)
ack := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
uint16(1024+i), 80, uint16(conntrack.TCPAck))
- manager.processOutgoingHooks(ack)
+ manager.processOutgoingHooks(ack, 0)
}
// Pre-generate test packets
@@ -856,8 +856,8 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
counter++
// Simulate bidirectional traffic
- manager.processOutgoingHooks(outPackets[connIdx])
- manager.dropFilter(inPackets[connIdx])
+ manager.processOutgoingHooks(outPackets[connIdx], 0)
+ manager.dropFilter(inPackets[connIdx], 0)
}
})
})
@@ -879,7 +879,7 @@ func BenchmarkParallelShortLivedConnections(b *testing.B) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, false, flowLogger)
defer b.Cleanup(func() {
- require.NoError(b, manager.Reset(nil))
+ require.NoError(b, manager.Close(nil))
})
manager.SetNetwork(&net.IPNet{
@@ -950,17 +950,17 @@ func BenchmarkParallelShortLivedConnections(b *testing.B) {
p := patterns[connIdx]
// Full connection lifecycle
- manager.processOutgoingHooks(p.syn)
- manager.dropFilter(p.synAck)
- manager.processOutgoingHooks(p.ack)
+ manager.processOutgoingHooks(p.syn, 0)
+ manager.dropFilter(p.synAck, 0)
+ manager.processOutgoingHooks(p.ack, 0)
- manager.processOutgoingHooks(p.request)
- manager.dropFilter(p.response)
+ manager.processOutgoingHooks(p.request, 0)
+ manager.dropFilter(p.response, 0)
- manager.processOutgoingHooks(p.finClient)
- manager.dropFilter(p.ackServer)
- manager.dropFilter(p.finServer)
- manager.processOutgoingHooks(p.ackClient)
+ manager.processOutgoingHooks(p.finClient, 0)
+ manager.dropFilter(p.ackServer, 0)
+ manager.dropFilter(p.finServer, 0)
+ manager.processOutgoingHooks(p.ackClient, 0)
}
})
})
@@ -1054,8 +1054,8 @@ func BenchmarkRouteACLs(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tc := range cases {
- srcIP := net.ParseIP(tc.srcIP)
- dstIP := net.ParseIP(tc.dstIP)
+ srcIP := netip.MustParseAddr(tc.srcIP)
+ dstIP := netip.MustParseAddr(tc.dstIP)
manager.routeACLsPass(srcIP, dstIP, tc.proto, 0, tc.dstPort)
}
}
diff --git a/client/firewall/uspfilter/uspfilter_filter_test.go b/client/firewall/uspfilter/uspfilter_filter_test.go
index d3dbef126..ba97c2643 100644
--- a/client/firewall/uspfilter/uspfilter_filter_test.go
+++ b/client/firewall/uspfilter/uspfilter_filter_test.go
@@ -12,9 +12,9 @@ import (
wgdevice "golang.zx2c4.com/wireguard/device"
fw "github.com/netbirdio/netbird/client/firewall/manager"
- "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/mocks"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
func TestPeerACLFiltering(t *testing.T) {
@@ -26,8 +26,8 @@ func TestPeerACLFiltering(t *testing.T) {
ifaceMock := &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: localIP,
Network: wgNet,
}
@@ -39,7 +39,7 @@ func TestPeerACLFiltering(t *testing.T) {
require.NotNil(t, manager)
t.Cleanup(func() {
- require.NoError(t, manager.Reset(nil))
+ require.NoError(t, manager.Close(nil))
})
manager.wgNetwork = wgNet
@@ -192,7 +192,7 @@ func TestPeerACLFiltering(t *testing.T) {
t.Run("Implicit DROP (no rules)", func(t *testing.T) {
packet := createTestPacket(t, "100.10.0.1", "100.10.0.100", fw.ProtocolTCP, 12345, 443)
- isDropped := manager.DropIncoming(packet)
+ isDropped := manager.DropIncoming(packet, 0)
require.True(t, isDropped, "Packet should be dropped when no rules exist")
})
@@ -217,7 +217,7 @@ func TestPeerACLFiltering(t *testing.T) {
})
packet := createTestPacket(t, tc.srcIP, tc.dstIP, tc.proto, tc.srcPort, tc.dstPort)
- isDropped := manager.DropIncoming(packet)
+ isDropped := manager.DropIncoming(packet, 0)
require.Equal(t, tc.shouldBeBlocked, isDropped)
})
}
@@ -288,8 +288,8 @@ func setupRoutedManager(tb testing.TB, network string) *Manager {
ifaceMock := &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: localIP,
Network: wgNet,
}
@@ -306,11 +306,11 @@ func setupRoutedManager(tb testing.TB, network string) *Manager {
require.NoError(tb, manager.EnableRouting())
require.NoError(tb, err)
require.NotNil(tb, manager)
- require.True(tb, manager.routingEnabled)
- require.False(tb, manager.nativeRouter)
+ require.True(tb, manager.routingEnabled.Load())
+ require.False(tb, manager.nativeRouter.Load())
tb.Cleanup(func() {
- require.NoError(tb, manager.Reset(nil))
+ require.NoError(tb, manager.Close(nil))
})
return manager
@@ -818,8 +818,8 @@ func TestRouteACLFiltering(t *testing.T) {
require.NoError(t, manager.DeleteRouteRule(rule))
})
- srcIP := net.ParseIP(tc.srcIP)
- dstIP := net.ParseIP(tc.dstIP)
+ srcIP := netip.MustParseAddr(tc.srcIP)
+ dstIP := netip.MustParseAddr(tc.dstIP)
// testing routeACLsPass only and not DropIncoming, as routed packets are dropped after being passed
// to the forwarder
@@ -1006,8 +1006,8 @@ func TestRouteACLOrder(t *testing.T) {
})
for i, p := range tc.packets {
- srcIP := net.ParseIP(p.srcIP)
- dstIP := net.ParseIP(p.dstIP)
+ srcIP := netip.MustParseAddr(p.srcIP)
+ dstIP := netip.MustParseAddr(p.dstIP)
_, isAllowed := manager.routeACLsPass(srcIP, dstIP, p.proto, p.srcPort, p.dstPort)
require.Equal(t, p.shouldPass, isAllowed, "packet %d failed", i)
diff --git a/client/firewall/uspfilter/uspfilter_test.go b/client/firewall/uspfilter/uspfilter_test.go
index e0e2b86c7..a095a5e39 100644
--- a/client/firewall/uspfilter/uspfilter_test.go
+++ b/client/firewall/uspfilter/uspfilter_test.go
@@ -18,17 +18,17 @@ import (
fw "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
"github.com/netbirdio/netbird/client/firewall/uspfilter/log"
- "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/netflow"
)
var logger = log.NewFromLogrus(logrus.StandardLogger())
-var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}).GetLogger()
+var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}, nil).GetLogger()
type IFaceMock struct {
SetFilterFunc func(device.PacketFilter) error
- AddressFunc func() iface.WGAddress
+ AddressFunc func() wgaddr.Address
GetWGDeviceFunc func() *wgdevice.Device
GetDeviceFunc func() *device.FilteredDevice
}
@@ -54,9 +54,9 @@ func (i *IFaceMock) SetFilter(iface device.PacketFilter) error {
return i.SetFilterFunc(iface)
}
-func (i *IFaceMock) Address() iface.WGAddress {
+func (i *IFaceMock) Address() wgaddr.Address {
if i.AddressFunc == nil {
- return iface.WGAddress{}
+ return wgaddr.Address{}
}
return i.AddressFunc()
}
@@ -125,19 +125,19 @@ func TestManagerDeleteRule(t *testing.T) {
return
}
- ip := net.ParseIP("192.168.1.1")
+ ip := netip.MustParseAddr("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []uint16{80}}
action := fw.ActionDrop
- rule2, err := m.AddPeerFiltering(nil, ip, proto, nil, port, action, "")
+ rule2, err := m.AddPeerFiltering(nil, ip.AsSlice(), proto, nil, port, action, "")
if err != nil {
t.Errorf("failed to add filtering: %v", err)
return
}
for _, r := range rule2 {
- if _, ok := m.incomingRules[ip.String()][r.ID()]; !ok {
+ if _, ok := m.incomingRules[ip][r.ID()]; !ok {
t.Errorf("rule2 is not in the incomingRules")
}
}
@@ -151,7 +151,7 @@ func TestManagerDeleteRule(t *testing.T) {
}
for _, r := range rule2 {
- if _, ok := m.incomingRules[ip.String()][r.ID()]; ok {
+ if _, ok := m.incomingRules[ip][r.ID()]; ok {
t.Errorf("rule2 is not in the incomingRules")
}
}
@@ -162,7 +162,7 @@ func TestAddUDPPacketHook(t *testing.T) {
name string
in bool
expDir fw.RuleDirection
- ip net.IP
+ ip netip.Addr
dPort uint16
hook func([]byte) bool
expectedID string
@@ -171,7 +171,7 @@ func TestAddUDPPacketHook(t *testing.T) {
name: "Test Outgoing UDP Packet Hook",
in: false,
expDir: fw.RuleDirectionOUT,
- ip: net.IPv4(10, 168, 0, 1),
+ ip: netip.MustParseAddr("10.168.0.1"),
dPort: 8000,
hook: func([]byte) bool { return true },
},
@@ -179,7 +179,7 @@ func TestAddUDPPacketHook(t *testing.T) {
name: "Test Incoming UDP Packet Hook",
in: true,
expDir: fw.RuleDirectionIN,
- ip: net.IPv6loopback,
+ ip: netip.MustParseAddr("::1"),
dPort: 9000,
hook: func([]byte) bool { return false },
},
@@ -196,11 +196,11 @@ func TestAddUDPPacketHook(t *testing.T) {
var addedRule PeerRule
if tt.in {
- if len(manager.incomingRules[tt.ip.String()]) != 1 {
+ if len(manager.incomingRules[tt.ip]) != 1 {
t.Errorf("expected 1 incoming rule, got %d", len(manager.incomingRules))
return
}
- for _, rule := range manager.incomingRules[tt.ip.String()] {
+ for _, rule := range manager.incomingRules[tt.ip] {
addedRule = rule
}
} else {
@@ -208,12 +208,12 @@ func TestAddUDPPacketHook(t *testing.T) {
t.Errorf("expected 1 outgoing rule, got %d", len(manager.outgoingRules))
return
}
- for _, rule := range manager.outgoingRules[tt.ip.String()] {
+ for _, rule := range manager.outgoingRules[tt.ip] {
addedRule = rule
}
}
- if !tt.ip.Equal(addedRule.ip) {
+ if tt.ip.Compare(addedRule.ip) != 0 {
t.Errorf("expected ip %s, got %s", tt.ip, addedRule.ip)
return
}
@@ -255,7 +255,7 @@ func TestManagerReset(t *testing.T) {
return
}
- err = m.Reset(nil)
+ err = m.Close(nil)
if err != nil {
t.Errorf("failed to reset Manager: %v", err)
return
@@ -269,8 +269,8 @@ func TestManagerReset(t *testing.T) {
func TestNotMatchByIP(t *testing.T) {
ifaceMock := &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("100.10.0.100"),
Network: &net.IPNet{
IP: net.ParseIP("100.10.0.0"),
@@ -328,12 +328,12 @@ func TestNotMatchByIP(t *testing.T) {
return
}
- if m.dropFilter(buf.Bytes()) {
+ if m.dropFilter(buf.Bytes(), 0) {
t.Errorf("expected packet to be accepted")
return
}
- if err = m.Reset(nil); err != nil {
+ if err = m.Close(nil); err != nil {
t.Errorf("failed to reset Manager: %v", err)
return
}
@@ -352,12 +352,12 @@ func TestRemovePacketHook(t *testing.T) {
t.Fatalf("Failed to create Manager: %s", err)
}
defer func() {
- require.NoError(t, manager.Reset(nil))
+ require.NoError(t, manager.Close(nil))
}()
// Add a UDP packet hook
hookFunc := func(data []byte) bool { return true }
- hookID := manager.AddUDPPacketHook(false, net.IPv4(192, 168, 0, 1), 8080, hookFunc)
+ hookID := manager.AddUDPPacketHook(false, netip.MustParseAddr("192.168.0.1"), 8080, hookFunc)
// Assert the hook is added by finding it in the manager's outgoing rules
found := false
@@ -403,7 +403,7 @@ func TestProcessOutgoingHooks(t *testing.T) {
manager.udpTracker.Close()
manager.udpTracker = conntrack.NewUDPTracker(100*time.Millisecond, logger, flowLogger)
defer func() {
- require.NoError(t, manager.Reset(nil))
+ require.NoError(t, manager.Close(nil))
}()
manager.decoders = sync.Pool{
@@ -423,7 +423,7 @@ func TestProcessOutgoingHooks(t *testing.T) {
hookCalled := false
hookID := manager.AddUDPPacketHook(
false,
- net.ParseIP("100.10.0.100"),
+ netip.MustParseAddr("100.10.0.100"),
53,
func([]byte) bool {
hookCalled = true
@@ -458,7 +458,7 @@ func TestProcessOutgoingHooks(t *testing.T) {
require.NoError(t, err)
// Test hook gets called
- result := manager.processOutgoingHooks(buf.Bytes())
+ result := manager.processOutgoingHooks(buf.Bytes(), 0)
require.True(t, result)
require.True(t, hookCalled)
@@ -468,7 +468,7 @@ func TestProcessOutgoingHooks(t *testing.T) {
err = gopacket.SerializeLayers(buf, opts, ipv4)
require.NoError(t, err)
- result = manager.processOutgoingHooks(buf.Bytes())
+ result = manager.processOutgoingHooks(buf.Bytes(), 0)
require.False(t, result)
}
@@ -484,7 +484,7 @@ func TestUSPFilterCreatePerformance(t *testing.T) {
time.Sleep(time.Second)
defer func() {
- if err := manager.Reset(nil); err != nil {
+ if err := manager.Close(nil); err != nil {
t.Errorf("clear the manager state: %v", err)
}
time.Sleep(time.Second)
@@ -530,7 +530,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
},
}
defer func() {
- require.NoError(t, manager.Reset(nil))
+ require.NoError(t, manager.Close(nil))
}()
// Set up packet parameters
@@ -569,11 +569,11 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
require.NoError(t, err)
// Process outbound packet and verify connection tracking
- drop := manager.DropOutgoing(outboundBuf.Bytes())
+ drop := manager.DropOutgoing(outboundBuf.Bytes(), 0)
require.False(t, drop, "Initial outbound packet should not be dropped")
// Verify connection was tracked
- conn, exists := manager.udpTracker.GetConnection(srcIP.AsSlice(), srcPort, dstIP.AsSlice(), dstPort)
+ conn, exists := manager.udpTracker.GetConnection(srcIP, srcPort, dstIP, dstPort)
require.True(t, exists, "Connection should be tracked after outbound packet")
require.True(t, srcIP.Compare(conn.SourceIP) == 0, "Source IP should match")
@@ -636,12 +636,12 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
for _, cp := range checkPoints {
time.Sleep(cp.sleep)
- drop = manager.dropFilter(inboundBuf.Bytes())
+ drop = manager.dropFilter(inboundBuf.Bytes(), 0)
require.Equal(t, cp.shouldAllow, !drop, cp.description)
// If the connection should still be valid, verify it exists
if cp.shouldAllow {
- conn, exists := manager.udpTracker.GetConnection(srcIP.AsSlice(), srcPort, dstIP.AsSlice(), dstPort)
+ conn, exists := manager.udpTracker.GetConnection(srcIP, srcPort, dstIP, dstPort)
require.True(t, exists, "Connection should still exist during valid window")
require.True(t, time.Since(conn.GetLastSeen()) < manager.udpTracker.Timeout(),
"LastSeen should be updated for valid responses")
@@ -685,7 +685,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
}
// Create a new outbound connection for invalid tests
- drop = manager.processOutgoingHooks(outboundBuf.Bytes())
+ drop = manager.processOutgoingHooks(outboundBuf.Bytes(), 0)
require.False(t, drop, "Second outbound packet should not be dropped")
for _, tc := range invalidCases {
@@ -707,7 +707,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
require.NoError(t, err)
// Verify the invalid packet is dropped
- drop = manager.dropFilter(testBuf.Bytes())
+ drop = manager.dropFilter(testBuf.Bytes(), 0)
require.True(t, drop, tc.description)
})
}
diff --git a/client/iface/bind/ice_bind.go b/client/iface/bind/ice_bind.go
index 41f415af7..66ec6a00d 100644
--- a/client/iface/bind/ice_bind.go
+++ b/client/iface/bind/ice_bind.go
@@ -5,7 +5,6 @@ import (
"net"
"net/netip"
"runtime"
- "strings"
"sync"
"github.com/pion/stun/v2"
@@ -14,6 +13,8 @@ import (
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
wgConn "golang.zx2c4.com/wireguard/conn"
+
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type RecvMessage struct {
@@ -52,9 +53,10 @@ type ICEBind struct {
muUDPMux sync.Mutex
udpMux *UniversalUDPMuxDefault
+ address wgaddr.Address
}
-func NewICEBind(transportNet transport.Net, filterFn FilterFn) *ICEBind {
+func NewICEBind(transportNet transport.Net, filterFn FilterFn, address wgaddr.Address) *ICEBind {
b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind)
ib := &ICEBind{
StdNetBind: b,
@@ -64,6 +66,7 @@ func NewICEBind(transportNet transport.Net, filterFn FilterFn) *ICEBind {
endpoints: make(map[netip.Addr]net.Conn),
closedChan: make(chan struct{}),
closed: true,
+ address: address,
}
rc := receiverCreator{
@@ -108,35 +111,17 @@ func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
return s.udpMux, nil
}
-func (b *ICEBind) SetEndpoint(peerAddress *net.UDPAddr, conn net.Conn) (*net.UDPAddr, error) {
- fakeUDPAddr, err := fakeAddress(peerAddress)
- if err != nil {
- return nil, err
- }
-
- // force IPv4
- fakeAddr, ok := netip.AddrFromSlice(fakeUDPAddr.IP.To4())
- if !ok {
- return nil, fmt.Errorf("failed to convert IP to netip.Addr")
- }
-
+func (b *ICEBind) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {
b.endpointsMu.Lock()
- b.endpoints[fakeAddr] = conn
+ b.endpoints[fakeIP] = conn
b.endpointsMu.Unlock()
-
- return fakeUDPAddr, nil
}
-func (b *ICEBind) RemoveEndpoint(fakeUDPAddr *net.UDPAddr) {
- fakeAddr, ok := netip.AddrFromSlice(fakeUDPAddr.IP.To4())
- if !ok {
- log.Warnf("failed to convert IP to netip.Addr")
- return
- }
-
+func (b *ICEBind) RemoveEndpoint(fakeIP netip.Addr) {
b.endpointsMu.Lock()
defer b.endpointsMu.Unlock()
- delete(b.endpoints, fakeAddr)
+
+ delete(b.endpoints, fakeIP)
}
func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error {
@@ -161,9 +146,10 @@ func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, r
s.udpMux = NewUniversalUDPMuxDefault(
UniversalUDPMuxParams{
- UDPConn: conn,
- Net: s.transportNet,
- FilterFn: s.filterFn,
+ UDPConn: conn,
+ Net: s.transportNet,
+ FilterFn: s.filterFn,
+ WGAddress: s.address,
},
)
return func(bufs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) {
@@ -275,21 +261,6 @@ func (c *ICEBind) receiveRelayed(buffs [][]byte, sizes []int, eps []wgConn.Endpo
}
}
-// fakeAddress returns a fake address that is used to as an identifier for the peer.
-// The fake address is in the format of 127.1.x.x where x.x is the last two octets of the peer address.
-func fakeAddress(peerAddress *net.UDPAddr) (*net.UDPAddr, error) {
- octets := strings.Split(peerAddress.IP.String(), ".")
- if len(octets) != 4 {
- return nil, fmt.Errorf("invalid IP format")
- }
-
- newAddr := &net.UDPAddr{
- IP: net.ParseIP(fmt.Sprintf("127.1.%s.%s", octets[2], octets[3])),
- Port: peerAddress.Port,
- }
- return newAddr, nil
-}
-
func getMessages(msgsPool *sync.Pool) *[]ipv6.Message {
return msgsPool.Get().(*[]ipv6.Message)
}
diff --git a/client/iface/bind/udp_mux_universal.go b/client/iface/bind/udp_mux_universal.go
index ebbefe035..6f851393e 100644
--- a/client/iface/bind/udp_mux_universal.go
+++ b/client/iface/bind/udp_mux_universal.go
@@ -17,6 +17,8 @@ import (
"github.com/pion/logging"
"github.com/pion/stun/v2"
"github.com/pion/transport/v3"
+
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// FilterFn is a function that filters out candidates based on the address.
@@ -41,6 +43,7 @@ type UniversalUDPMuxParams struct {
XORMappedAddrCacheTTL time.Duration
Net transport.Net
FilterFn FilterFn
+ WGAddress wgaddr.Address
}
// NewUniversalUDPMuxDefault creates an implementation of UniversalUDPMux embedding UDPMux
@@ -64,6 +67,7 @@ func NewUniversalUDPMuxDefault(params UniversalUDPMuxParams) *UniversalUDPMuxDef
mux: m,
logger: params.Logger,
filterFn: params.FilterFn,
+ address: params.WGAddress,
}
// embed UDPMux
@@ -118,6 +122,7 @@ type udpConn struct {
filterFn FilterFn
// TODO: reset cache on route changes
addrCache sync.Map
+ address wgaddr.Address
}
func (u *udpConn) WriteTo(b []byte, addr net.Addr) (int, error) {
@@ -159,6 +164,11 @@ func (u *udpConn) performFilterCheck(addr net.Addr) error {
return nil
}
+ if u.address.Network.Contains(a.AsSlice()) {
+ log.Warnf("Address %s is part of the NetBird network %s, refusing to write", addr, u.address)
+ return fmt.Errorf("address %s is part of the NetBird network %s, refusing to write", addr, u.address)
+ }
+
if isRouted, prefix, err := u.filterFn(a); err != nil {
log.Errorf("Failed to check if address %s is routed: %v", addr, err)
} else {
diff --git a/client/iface/device.go b/client/iface/device.go
index 86e9dab4b..81f2e0f47 100644
--- a/client/iface/device.go
+++ b/client/iface/device.go
@@ -9,13 +9,14 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type WGTunDevice interface {
Create() (device.WGConfigurer, error)
Up() (*bind.UniversalUDPMuxDefault, error)
- UpdateAddr(address WGAddress) error
- WgAddress() WGAddress
+ UpdateAddr(address wgaddr.Address) error
+ WgAddress() wgaddr.Address
DeviceName() string
Close() error
FilteredDevice() *device.FilteredDevice
diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go
index 55081e181..ab3e611e1 100644
--- a/client/iface/device/device_android.go
+++ b/client/iface/device/device_android.go
@@ -13,11 +13,12 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// WGTunDevice ignore the WGTunDevice interface on Android because the creation of the tun device is different on this platform
type WGTunDevice struct {
- address WGAddress
+ address wgaddr.Address
port int
key string
mtu int
@@ -31,7 +32,7 @@ type WGTunDevice struct {
configurer WGConfigurer
}
-func NewTunDevice(address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind, tunAdapter TunAdapter) *WGTunDevice {
+func NewTunDevice(address wgaddr.Address, port int, key string, mtu int, iceBind *bind.ICEBind, tunAdapter TunAdapter) *WGTunDevice {
return &WGTunDevice{
address: address,
port: port,
@@ -93,7 +94,7 @@ func (t *WGTunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return udpMux, nil
}
-func (t *WGTunDevice) UpdateAddr(addr WGAddress) error {
+func (t *WGTunDevice) UpdateAddr(addr wgaddr.Address) error {
// todo implement
return nil
}
@@ -123,7 +124,7 @@ func (t *WGTunDevice) DeviceName() string {
return t.name
}
-func (t *WGTunDevice) WgAddress() WGAddress {
+func (t *WGTunDevice) WgAddress() wgaddr.Address {
return t.address
}
diff --git a/client/iface/device/device_darwin.go b/client/iface/device/device_darwin.go
index 1a5635ff2..01bfbf381 100644
--- a/client/iface/device/device_darwin.go
+++ b/client/iface/device/device_darwin.go
@@ -13,11 +13,12 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type TunDevice struct {
name string
- address WGAddress
+ address wgaddr.Address
port int
key string
mtu int
@@ -29,7 +30,7 @@ type TunDevice struct {
configurer WGConfigurer
}
-func NewTunDevice(name string, address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind) *TunDevice {
+func NewTunDevice(name string, address wgaddr.Address, port int, key string, mtu int, iceBind *bind.ICEBind) *TunDevice {
return &TunDevice{
name: name,
address: address,
@@ -85,7 +86,7 @@ func (t *TunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return udpMux, nil
}
-func (t *TunDevice) UpdateAddr(address WGAddress) error {
+func (t *TunDevice) UpdateAddr(address wgaddr.Address) error {
t.address = address
return t.assignAddr()
}
@@ -106,7 +107,7 @@ func (t *TunDevice) Close() error {
return nil
}
-func (t *TunDevice) WgAddress() WGAddress {
+func (t *TunDevice) WgAddress() wgaddr.Address {
return t.address
}
diff --git a/client/iface/device/device_filter.go b/client/iface/device/device_filter.go
index f87f10429..c9b7e2448 100644
--- a/client/iface/device/device_filter.go
+++ b/client/iface/device/device_filter.go
@@ -2,6 +2,7 @@ package device
import (
"net"
+ "net/netip"
"sync"
"golang.zx2c4.com/wireguard/tun"
@@ -10,16 +11,16 @@ import (
// PacketFilter interface for firewall abilities
type PacketFilter interface {
// DropOutgoing filter outgoing packets from host to external destinations
- DropOutgoing(packetData []byte) bool
+ DropOutgoing(packetData []byte, size int) bool
// DropIncoming filter incoming packets from external sources to host
- DropIncoming(packetData []byte) bool
+ DropIncoming(packetData []byte, size int) bool
// AddUDPPacketHook calls hook when UDP packet from given direction matched
//
// Hook function returns flag which indicates should be the matched package dropped or not.
// Hook function receives raw network packet data as argument.
- AddUDPPacketHook(in bool, ip net.IP, dPort uint16, hook func(packet []byte) bool) string
+ AddUDPPacketHook(in bool, ip netip.Addr, dPort uint16, hook func(packet []byte) bool) string
// RemovePacketHook removes hook by ID
RemovePacketHook(hookID string) error
@@ -57,7 +58,7 @@ func (d *FilteredDevice) Read(bufs [][]byte, sizes []int, offset int) (n int, er
}
for i := 0; i < n; i++ {
- if filter.DropOutgoing(bufs[i][offset : offset+sizes[i]]) {
+ if filter.DropOutgoing(bufs[i][offset:offset+sizes[i]], sizes[i]) {
bufs = append(bufs[:i], bufs[i+1:]...)
sizes = append(sizes[:i], sizes[i+1:]...)
n--
@@ -81,7 +82,7 @@ func (d *FilteredDevice) Write(bufs [][]byte, offset int) (int, error) {
filteredBufs := make([][]byte, 0, len(bufs))
dropped := 0
for _, buf := range bufs {
- if !filter.DropIncoming(buf[offset:]) {
+ if !filter.DropIncoming(buf[offset:], len(buf)) {
filteredBufs = append(filteredBufs, buf)
dropped++
}
diff --git a/client/iface/device/device_filter_test.go b/client/iface/device/device_filter_test.go
index d3278b918..c90269e82 100644
--- a/client/iface/device/device_filter_test.go
+++ b/client/iface/device/device_filter_test.go
@@ -146,7 +146,7 @@ func TestDeviceWrapperRead(t *testing.T) {
tun.EXPECT().Write(mockBufs, 0).Return(0, nil)
filter := mocks.NewMockPacketFilter(ctrl)
- filter.EXPECT().DropIncoming(gomock.Any()).Return(true)
+ filter.EXPECT().DropIncoming(gomock.Any(), gomock.Any()).Return(true)
wrapped := newDeviceFilter(tun)
wrapped.filter = filter
@@ -201,7 +201,7 @@ func TestDeviceWrapperRead(t *testing.T) {
return 1, nil
})
filter := mocks.NewMockPacketFilter(ctrl)
- filter.EXPECT().DropOutgoing(gomock.Any()).Return(true)
+ filter.EXPECT().DropOutgoing(gomock.Any(), gomock.Any()).Return(true)
wrapped := newDeviceFilter(tun)
wrapped.filter = filter
diff --git a/client/iface/device/device_ios.go b/client/iface/device/device_ios.go
index b106d475c..56d44d68e 100644
--- a/client/iface/device/device_ios.go
+++ b/client/iface/device/device_ios.go
@@ -14,11 +14,12 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type TunDevice struct {
name string
- address WGAddress
+ address wgaddr.Address
port int
key string
iceBind *bind.ICEBind
@@ -30,7 +31,7 @@ type TunDevice struct {
configurer WGConfigurer
}
-func NewTunDevice(name string, address WGAddress, port int, key string, iceBind *bind.ICEBind, tunFd int) *TunDevice {
+func NewTunDevice(name string, address wgaddr.Address, port int, key string, iceBind *bind.ICEBind, tunFd int) *TunDevice {
return &TunDevice{
name: name,
address: address,
@@ -120,11 +121,11 @@ func (t *TunDevice) Close() error {
return nil
}
-func (t *TunDevice) WgAddress() WGAddress {
+func (t *TunDevice) WgAddress() wgaddr.Address {
return t.address
}
-func (t *TunDevice) UpdateAddr(addr WGAddress) error {
+func (t *TunDevice) UpdateAddr(_ wgaddr.Address) error {
// todo implement
return nil
}
diff --git a/client/iface/device/device_kernel_unix.go b/client/iface/device/device_kernel_unix.go
index fe1d1147f..988ed1b39 100644
--- a/client/iface/device/device_kernel_unix.go
+++ b/client/iface/device/device_kernel_unix.go
@@ -14,12 +14,13 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/sharedsock"
)
type TunKernelDevice struct {
name string
- address WGAddress
+ address wgaddr.Address
wgPort int
key string
mtu int
@@ -34,7 +35,7 @@ type TunKernelDevice struct {
filterFn bind.FilterFn
}
-func NewKernelDevice(name string, address WGAddress, wgPort int, key string, mtu int, transportNet transport.Net) *TunKernelDevice {
+func NewKernelDevice(name string, address wgaddr.Address, wgPort int, key string, mtu int, transportNet transport.Net) *TunKernelDevice {
ctx, cancel := context.WithCancel(context.Background())
return &TunKernelDevice{
ctx: ctx,
@@ -99,9 +100,10 @@ func (t *TunKernelDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return nil, err
}
bindParams := bind.UniversalUDPMuxParams{
- UDPConn: rawSock,
- Net: t.transportNet,
- FilterFn: t.filterFn,
+ UDPConn: rawSock,
+ Net: t.transportNet,
+ FilterFn: t.filterFn,
+ WGAddress: t.address,
}
mux := bind.NewUniversalUDPMuxDefault(bindParams)
go mux.ReadFromConn(t.ctx)
@@ -112,7 +114,7 @@ func (t *TunKernelDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return t.udpMux, nil
}
-func (t *TunKernelDevice) UpdateAddr(address WGAddress) error {
+func (t *TunKernelDevice) UpdateAddr(address wgaddr.Address) error {
t.address = address
return t.assignAddr()
}
@@ -145,7 +147,7 @@ func (t *TunKernelDevice) Close() error {
return closErr
}
-func (t *TunKernelDevice) WgAddress() WGAddress {
+func (t *TunKernelDevice) WgAddress() wgaddr.Address {
return t.address
}
diff --git a/client/iface/device/device_netstack.go b/client/iface/device/device_netstack.go
index 0cb02fd19..d3c92235e 100644
--- a/client/iface/device/device_netstack.go
+++ b/client/iface/device/device_netstack.go
@@ -13,12 +13,13 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
nbnet "github.com/netbirdio/netbird/util/net"
)
type TunNetstackDevice struct {
name string
- address WGAddress
+ address wgaddr.Address
port int
key string
mtu int
@@ -34,7 +35,7 @@ type TunNetstackDevice struct {
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 wgaddr.Address, wgPort int, key string, mtu int, iceBind *bind.ICEBind, listenAddress string) *TunNetstackDevice {
return &TunNetstackDevice{
name: name,
address: address,
@@ -97,7 +98,7 @@ func (t *TunNetstackDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return udpMux, nil
}
-func (t *TunNetstackDevice) UpdateAddr(WGAddress) error {
+func (t *TunNetstackDevice) UpdateAddr(wgaddr.Address) error {
return nil
}
@@ -116,7 +117,7 @@ func (t *TunNetstackDevice) Close() error {
return nil
}
-func (t *TunNetstackDevice) WgAddress() WGAddress {
+func (t *TunNetstackDevice) WgAddress() wgaddr.Address {
return t.address
}
diff --git a/client/iface/device/device_usp_unix.go b/client/iface/device/device_usp_unix.go
index 07570617a..c45ae9676 100644
--- a/client/iface/device/device_usp_unix.go
+++ b/client/iface/device/device_usp_unix.go
@@ -12,11 +12,12 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type USPDevice struct {
name string
- address WGAddress
+ address wgaddr.Address
port int
key string
mtu int
@@ -28,7 +29,7 @@ type USPDevice struct {
configurer WGConfigurer
}
-func NewUSPDevice(name string, address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind) *USPDevice {
+func NewUSPDevice(name string, address wgaddr.Address, port int, key string, mtu int, iceBind *bind.ICEBind) *USPDevice {
log.Infof("using userspace bind mode")
return &USPDevice{
@@ -93,7 +94,7 @@ func (t *USPDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return udpMux, nil
}
-func (t *USPDevice) UpdateAddr(address WGAddress) error {
+func (t *USPDevice) UpdateAddr(address wgaddr.Address) error {
t.address = address
return t.assignAddr()
}
@@ -113,7 +114,7 @@ func (t *USPDevice) Close() error {
return nil
}
-func (t *USPDevice) WgAddress() WGAddress {
+func (t *USPDevice) WgAddress() wgaddr.Address {
return t.address
}
diff --git a/client/iface/device/device_windows.go b/client/iface/device/device_windows.go
index 0fd1b3326..41e615bc2 100644
--- a/client/iface/device/device_windows.go
+++ b/client/iface/device/device_windows.go
@@ -13,13 +13,14 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
const defaultWindowsGUIDSTring = "{f2f29e61-d91f-4d76-8151-119b20c4bdeb}"
type TunDevice struct {
name string
- address WGAddress
+ address wgaddr.Address
port int
key string
mtu int
@@ -32,7 +33,7 @@ type TunDevice struct {
configurer WGConfigurer
}
-func NewTunDevice(name string, address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind) *TunDevice {
+func NewTunDevice(name string, address wgaddr.Address, port int, key string, mtu int, iceBind *bind.ICEBind) *TunDevice {
return &TunDevice{
name: name,
address: address,
@@ -118,7 +119,7 @@ func (t *TunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
return udpMux, nil
}
-func (t *TunDevice) UpdateAddr(address WGAddress) error {
+func (t *TunDevice) UpdateAddr(address wgaddr.Address) error {
t.address = address
return t.assignAddr()
}
@@ -139,7 +140,7 @@ func (t *TunDevice) Close() error {
}
return nil
}
-func (t *TunDevice) WgAddress() WGAddress {
+func (t *TunDevice) WgAddress() wgaddr.Address {
return t.address
}
diff --git a/client/iface/device/wg_link_freebsd.go b/client/iface/device/wg_link_freebsd.go
index 104010f47..9067790e4 100644
--- a/client/iface/device/wg_link_freebsd.go
+++ b/client/iface/device/wg_link_freebsd.go
@@ -6,6 +6,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/iface/freebsd"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type wgLink struct {
@@ -56,7 +57,7 @@ func (l *wgLink) up() error {
return nil
}
-func (l *wgLink) assignAddr(address WGAddress) error {
+func (l *wgLink) assignAddr(address wgaddr.Address) error {
link, err := freebsd.LinkByName(l.name)
if err != nil {
return fmt.Errorf("link by name: %w", err)
diff --git a/client/iface/device/wg_link_linux.go b/client/iface/device/wg_link_linux.go
index a15cffe48..d941cd022 100644
--- a/client/iface/device/wg_link_linux.go
+++ b/client/iface/device/wg_link_linux.go
@@ -8,6 +8,8 @@ import (
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
+
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type wgLink struct {
@@ -90,7 +92,7 @@ func (l *wgLink) up() error {
return nil
}
-func (l *wgLink) assignAddr(address WGAddress) error {
+func (l *wgLink) assignAddr(address wgaddr.Address) error {
//delete existing addresses
list, err := netlink.AddrList(l, 0)
if err != nil {
diff --git a/client/iface/device_android.go b/client/iface/device_android.go
index 5cbeb70f8..a1e246fc5 100644
--- a/client/iface/device_android.go
+++ b/client/iface/device_android.go
@@ -7,13 +7,14 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type WGTunDevice interface {
Create(routes []string, dns string, searchDomains []string) (device.WGConfigurer, error)
Up() (*bind.UniversalUDPMuxDefault, error)
- UpdateAddr(address WGAddress) error
- WgAddress() WGAddress
+ UpdateAddr(address wgaddr.Address) error
+ WgAddress() wgaddr.Address
DeviceName() string
Close() error
FilteredDevice() *device.FilteredDevice
diff --git a/client/iface/iface.go b/client/iface/iface.go
index 40bd51fbb..9d5262aed 100644
--- a/client/iface/iface.go
+++ b/client/iface/iface.go
@@ -19,6 +19,7 @@ import (
"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/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
@@ -28,8 +29,6 @@ const (
WgInterfaceDefault = configurer.WgInterfaceDefault
)
-type WGAddress = device.WGAddress
-
type wgProxyFactory interface {
GetProxy() wgproxy.Proxy
Free() error
@@ -72,7 +71,7 @@ func (w *WGIface) Name() string {
}
// Address returns the interface address
-func (w *WGIface) Address() device.WGAddress {
+func (w *WGIface) Address() wgaddr.Address {
return w.tun.WgAddress()
}
@@ -103,7 +102,7 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
w.mu.Lock()
defer w.mu.Unlock()
- addr, err := device.ParseWGAddress(newAddr)
+ addr, err := wgaddr.ParseWGAddress(newAddr)
if err != nil {
return err
}
diff --git a/client/iface/iface_new_android.go b/client/iface/iface_new_android.go
index 69a8d1fd4..35046b887 100644
--- a/client/iface/iface_new_android.go
+++ b/client/iface/iface_new_android.go
@@ -3,17 +3,18 @@ package iface
import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
- wgAddress, err := device.ParseWGAddress(opts.Address)
+ wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
if err != nil {
return nil, err
}
- iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn)
+ iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
wgIFace := &WGIface{
userspaceBind: true,
diff --git a/client/iface/iface_new_darwin.go b/client/iface/iface_new_darwin.go
index a92d74e0f..93fd7fd5c 100644
--- a/client/iface/iface_new_darwin.go
+++ b/client/iface/iface_new_darwin.go
@@ -6,17 +6,18 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/netstack"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
- wgAddress, err := device.ParseWGAddress(opts.Address)
+ wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
if err != nil {
return nil, err
}
- iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn)
+ iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
var tun WGTunDevice
if netstack.IsEnabled() {
diff --git a/client/iface/iface_new_ios.go b/client/iface/iface_new_ios.go
index 363f95e11..317ee0f46 100644
--- a/client/iface/iface_new_ios.go
+++ b/client/iface/iface_new_ios.go
@@ -5,17 +5,18 @@ package iface
import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
- wgAddress, err := device.ParseWGAddress(opts.Address)
+ wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
if err != nil {
return nil, err
}
- iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn)
+ iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
wgIFace := &WGIface{
tun: device.NewTunDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, iceBind, opts.MobileArgs.TunFd),
diff --git a/client/iface/iface_new_unix.go b/client/iface/iface_new_unix.go
index f10b17c9a..23ee7236f 100644
--- a/client/iface/iface_new_unix.go
+++ b/client/iface/iface_new_unix.go
@@ -8,12 +8,13 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/netstack"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
- wgAddress, err := device.ParseWGAddress(opts.Address)
+ wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
if err != nil {
return nil, err
}
@@ -21,7 +22,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
wgIFace := &WGIface{}
if netstack.IsEnabled() {
- iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn)
+ iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
wgIFace.tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr())
wgIFace.userspaceBind = true
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind)
@@ -34,7 +35,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
return wgIFace, nil
}
if device.ModuleTunIsLoaded() {
- iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn)
+ iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
wgIFace.tun = device.NewUSPDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind)
wgIFace.userspaceBind = true
wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind)
diff --git a/client/iface/iface_new_windows.go b/client/iface/iface_new_windows.go
index 2e6355496..413062940 100644
--- a/client/iface/iface_new_windows.go
+++ b/client/iface/iface_new_windows.go
@@ -4,16 +4,17 @@ import (
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/netstack"
+ wgaddr "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
- wgAddress, err := device.ParseWGAddress(opts.Address)
+ wgAddress, err := wgaddr.ParseWGAddress(opts.Address)
if err != nil {
return nil, err
}
- iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn)
+ iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn, wgAddress)
var tun WGTunDevice
if netstack.IsEnabled() {
diff --git a/client/iface/mocks/filter.go b/client/iface/mocks/filter.go
index 6348e0e77..faac55d68 100644
--- a/client/iface/mocks/filter.go
+++ b/client/iface/mocks/filter.go
@@ -6,6 +6,7 @@ package mocks
import (
net "net"
+ "net/netip"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
@@ -35,7 +36,7 @@ func (m *MockPacketFilter) EXPECT() *MockPacketFilterMockRecorder {
}
// AddUDPPacketHook mocks base method.
-func (m *MockPacketFilter) AddUDPPacketHook(arg0 bool, arg1 net.IP, arg2 uint16, arg3 func([]byte) bool) string {
+func (m *MockPacketFilter) AddUDPPacketHook(arg0 bool, arg1 netip.Addr, arg2 uint16, arg3 func([]byte) bool) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddUDPPacketHook", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(string)
@@ -49,31 +50,31 @@ func (mr *MockPacketFilterMockRecorder) AddUDPPacketHook(arg0, arg1, arg2, arg3
}
// DropIncoming mocks base method.
-func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
+func (m *MockPacketFilter) DropIncoming(arg0 []byte, arg1 int) bool {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "DropIncoming", arg0)
+ ret := m.ctrl.Call(m, "DropIncoming", arg0, arg1)
ret0, _ := ret[0].(bool)
return ret0
}
// DropIncoming indicates an expected call of DropIncoming.
-func (mr *MockPacketFilterMockRecorder) DropIncoming(arg0 interface{}) *gomock.Call {
+func (mr *MockPacketFilterMockRecorder) DropIncoming(arg0 interface{}, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0, arg1)
}
// DropOutgoing mocks base method.
-func (m *MockPacketFilter) DropOutgoing(arg0 []byte) bool {
+func (m *MockPacketFilter) DropOutgoing(arg0 []byte, arg1 int) bool {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "DropOutgoing", arg0)
+ ret := m.ctrl.Call(m, "DropOutgoing", arg0, arg1)
ret0, _ := ret[0].(bool)
return ret0
}
// DropOutgoing indicates an expected call of DropOutgoing.
-func (mr *MockPacketFilterMockRecorder) DropOutgoing(arg0 interface{}) *gomock.Call {
+func (mr *MockPacketFilterMockRecorder) DropOutgoing(arg0 interface{}, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0, arg1)
}
// RemovePacketHook mocks base method.
diff --git a/client/iface/netstack/tun.go b/client/iface/netstack/tun.go
index 01f19875e..a271a1954 100644
--- a/client/iface/netstack/tun.go
+++ b/client/iface/netstack/tun.go
@@ -55,7 +55,7 @@ func (t *NetStackTun) Create() (tun.Device, *netstack.Net, error) {
skipProxy, err := strconv.ParseBool(os.Getenv(EnvSkipProxy))
if err != nil {
- log.Errorf("failed to parse NB_ETSTACK_SKIP_PROXY: %s", err)
+ log.Errorf("failed to parse %s: %s", EnvSkipProxy, err)
}
if skipProxy {
return nsTunDev, tunNet, nil
diff --git a/client/iface/device/address.go b/client/iface/wgaddr/address.go
similarity index 61%
rename from client/iface/device/address.go
rename to client/iface/wgaddr/address.go
index 15de301da..e5079258c 100644
--- a/client/iface/device/address.go
+++ b/client/iface/wgaddr/address.go
@@ -1,29 +1,29 @@
-package device
+package wgaddr
import (
"fmt"
"net"
)
-// WGAddress WireGuard parsed address
-type WGAddress struct {
+// Address WireGuard parsed address
+type Address struct {
IP net.IP
Network *net.IPNet
}
// ParseWGAddress parse a string ("1.2.3.4/24") address to WG Address
-func ParseWGAddress(address string) (WGAddress, error) {
+func ParseWGAddress(address string) (Address, error) {
ip, network, err := net.ParseCIDR(address)
if err != nil {
- return WGAddress{}, err
+ return Address{}, err
}
- return WGAddress{
+ return Address{
IP: ip,
Network: network,
}, nil
}
-func (addr WGAddress) String() string {
+func (addr Address) String() string {
maskSize, _ := addr.Network.Mask.Size()
return fmt.Sprintf("%s/%d", addr.IP.String(), maskSize)
}
diff --git a/client/iface/wgproxy/bind/proxy.go b/client/iface/wgproxy/bind/proxy.go
index 8a2e65382..614787e17 100644
--- a/client/iface/wgproxy/bind/proxy.go
+++ b/client/iface/wgproxy/bind/proxy.go
@@ -6,6 +6,7 @@ import (
"fmt"
"net"
"net/netip"
+ "strings"
"sync"
log "github.com/sirupsen/logrus"
@@ -16,13 +17,13 @@ import (
type ProxyBind struct {
Bind *bind.ICEBind
- wgAddr *net.UDPAddr
- wgEndpoint *bind.Endpoint
- remoteConn net.Conn
- ctx context.Context
- cancel context.CancelFunc
- closeMu sync.Mutex
- closed bool
+ fakeNetIP *netip.AddrPort
+ wgBindEndpoint *bind.Endpoint
+ remoteConn net.Conn
+ ctx context.Context
+ cancel context.CancelFunc
+ closeMu sync.Mutex
+ closed bool
pausedMu sync.Mutex
paused bool
@@ -33,20 +34,24 @@ type ProxyBind struct {
// endpoint is the NetBird address of the remote peer. The SetEndpoint return with the address what will be used in the
// WireGuard configuration.
func (p *ProxyBind) AddTurnConn(ctx context.Context, nbAddr *net.UDPAddr, remoteConn net.Conn) error {
- addr, err := p.Bind.SetEndpoint(nbAddr, remoteConn)
+ fakeNetIP, err := fakeAddress(nbAddr)
if err != nil {
return err
}
- p.wgAddr = addr
- p.wgEndpoint = addrToEndpoint(addr)
+ p.fakeNetIP = fakeNetIP
+ p.wgBindEndpoint = &bind.Endpoint{AddrPort: *fakeNetIP}
p.remoteConn = remoteConn
p.ctx, p.cancel = context.WithCancel(ctx)
- return err
+ return nil
}
func (p *ProxyBind) EndpointAddr() *net.UDPAddr {
- return p.wgAddr
+ return &net.UDPAddr{
+ IP: p.fakeNetIP.Addr().AsSlice(),
+ Port: int(p.fakeNetIP.Port()),
+ Zone: p.fakeNetIP.Addr().Zone(),
+ }
}
func (p *ProxyBind) Work() {
@@ -54,6 +59,8 @@ func (p *ProxyBind) Work() {
return
}
+ p.Bind.SetEndpoint(p.fakeNetIP.Addr(), p.remoteConn)
+
p.pausedMu.Lock()
p.paused = false
p.pausedMu.Unlock()
@@ -93,7 +100,7 @@ func (p *ProxyBind) close() error {
p.cancel()
- p.Bind.RemoveEndpoint(p.wgAddr)
+ p.Bind.RemoveEndpoint(p.fakeNetIP.Addr())
if rErr := p.remoteConn.Close(); rErr != nil && !errors.Is(rErr, net.ErrClosed) {
return rErr
@@ -126,7 +133,7 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
}
msg := bind.RecvMessage{
- Endpoint: p.wgEndpoint,
+ Endpoint: p.wgBindEndpoint,
Buffer: buf[:n],
}
p.Bind.RecvChan <- msg
@@ -134,8 +141,19 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
}
}
-func addrToEndpoint(addr *net.UDPAddr) *bind.Endpoint {
- ip, _ := netip.AddrFromSlice(addr.IP.To4())
- addrPort := netip.AddrPortFrom(ip, uint16(addr.Port))
- return &bind.Endpoint{AddrPort: addrPort}
+// fakeAddress returns a fake address that is used to as an identifier for the peer.
+// The fake address is in the format of 127.1.x.x where x.x is the last two octets of the peer address.
+func fakeAddress(peerAddress *net.UDPAddr) (*netip.AddrPort, error) {
+ octets := strings.Split(peerAddress.IP.String(), ".")
+ if len(octets) != 4 {
+ return nil, fmt.Errorf("invalid IP format")
+ }
+
+ fakeIP, err := netip.ParseAddr(fmt.Sprintf("127.1.%s.%s", octets[2], octets[3]))
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse new IP: %w", err)
+ }
+
+ netipAddr := netip.AddrPortFrom(fakeIP, uint16(peerAddress.Port))
+ return &netipAddr, nil
}
diff --git a/client/installer.nsis b/client/installer.nsis
index af942a868..5219058a8 100644
--- a/client/installer.nsis
+++ b/client/installer.nsis
@@ -6,8 +6,8 @@
!define DESCRIPTION "A WireGuard®-based mesh network that connects your devices into a single private network"
!define INSTALLER_NAME "netbird-installer.exe"
!define MAIN_APP_EXE "Netbird"
-!define ICON "ui\\netbird.ico"
-!define BANNER "ui\\banner.bmp"
+!define ICON "ui\\assets\\netbird.ico"
+!define BANNER "ui\\build\\banner.bmp"
!define LICENSE_DATA "..\\LICENSE"
!define INSTALL_DIR "$PROGRAMFILES64\${APP_NAME}"
@@ -22,6 +22,8 @@
!define UI_REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${UI_APP_EXE}"
!define UI_UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UI_APP_NAME}"
+!define AUTOSTART_REG_KEY "Software\Microsoft\Windows\CurrentVersion\Run"
+
Unicode True
######################################################################
@@ -68,6 +70,9 @@ ShowInstDetails Show
!insertmacro MUI_PAGE_DIRECTORY
+; Custom page for autostart checkbox
+Page custom AutostartPage AutostartPageLeave
+
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
@@ -80,8 +85,36 @@ ShowInstDetails Show
!insertmacro MUI_LANGUAGE "English"
+; Variables for autostart option
+Var AutostartCheckbox
+Var AutostartEnabled
+
######################################################################
+; Function to create the autostart options page
+Function AutostartPage
+ !insertmacro MUI_HEADER_TEXT "Startup Options" "Configure how ${APP_NAME} launches with Windows."
+
+ nsDialogs::Create 1018
+ Pop $0
+
+ ${If} $0 == error
+ Abort
+ ${EndIf}
+
+ ${NSD_CreateCheckbox} 0 20u 100% 10u "Start ${APP_NAME} UI automatically when Windows starts"
+ Pop $AutostartCheckbox
+ ${NSD_Check} $AutostartCheckbox ; Default to checked
+ StrCpy $AutostartEnabled "1" ; Default to enabled
+
+ nsDialogs::Show
+FunctionEnd
+
+; Function to handle leaving the autostart page
+Function AutostartPageLeave
+ ${NSD_GetState} $AutostartCheckbox $AutostartEnabled
+FunctionEnd
+
Function GetAppFromCommand
Exch $1
Push $2
@@ -163,6 +196,16 @@ WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
WriteRegStr ${REG_ROOT} "${UI_REG_APP_PATH}" "" "$INSTDIR\${UI_APP_EXE}"
+; Create autostart registry entry based on checkbox
+DetailPrint "Autostart enabled: $AutostartEnabled"
+${If} $AutostartEnabled == "1"
+ WriteRegStr HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}" "$INSTDIR\${UI_APP_EXE}.exe"
+ DetailPrint "Added autostart registry entry: $INSTDIR\${UI_APP_EXE}.exe"
+${Else}
+ DeleteRegValue HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}"
+ DetailPrint "Autostart not enabled by user"
+${EndIf}
+
EnVar::SetHKLM
EnVar::AddValueEx "path" "$INSTDIR"
@@ -186,7 +229,10 @@ ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service stop'
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
# kill ui client
-ExecWait `taskkill /im ${UI_APP_EXE}.exe`
+ExecWait `taskkill /im ${UI_APP_EXE}.exe /f`
+
+; Remove autostart registry entry
+DeleteRegValue HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}"
# wait the service uninstall take unblock the executable
Sleep 3000
diff --git a/client/internal/acl/manager_test.go b/client/internal/acl/manager_test.go
index 82a136e9c..ca79111ef 100644
--- a/client/internal/acl/manager_test.go
+++ b/client/internal/acl/manager_test.go
@@ -9,13 +9,13 @@ import (
"github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/client/firewall/manager"
- "github.com/netbirdio/netbird/client/iface"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/acl/mocks"
"github.com/netbirdio/netbird/client/internal/netflow"
mgmProto "github.com/netbirdio/netbird/management/proto"
)
-var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}).GetLogger()
+var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}, nil).GetLogger()
func TestDefaultManager(t *testing.T) {
networkMap := &mgmProto.NetworkMap{
@@ -49,7 +49,7 @@ func TestDefaultManager(t *testing.T) {
}
ifaceMock.EXPECT().Name().Return("lo").AnyTimes()
- ifaceMock.EXPECT().Address().Return(iface.WGAddress{
+ ifaceMock.EXPECT().Address().Return(wgaddr.Address{
IP: ip,
Network: network,
}).AnyTimes()
@@ -62,7 +62,7 @@ func TestDefaultManager(t *testing.T) {
return
}
defer func(fw manager.Manager) {
- _ = fw.Reset(nil)
+ _ = fw.Close(nil)
}(fw)
acl := NewDefaultManager(fw)
@@ -343,7 +343,7 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) {
}
ifaceMock.EXPECT().Name().Return("lo").AnyTimes()
- ifaceMock.EXPECT().Address().Return(iface.WGAddress{
+ ifaceMock.EXPECT().Address().Return(wgaddr.Address{
IP: ip,
Network: network,
}).AnyTimes()
@@ -356,7 +356,7 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) {
return
}
defer func(fw manager.Manager) {
- _ = fw.Reset(nil)
+ _ = fw.Close(nil)
}(fw)
acl := NewDefaultManager(fw)
diff --git a/client/internal/acl/mocks/iface_mapper.go b/client/internal/acl/mocks/iface_mapper.go
index 08aa4fd5a..95d5a2c58 100644
--- a/client/internal/acl/mocks/iface_mapper.go
+++ b/client/internal/acl/mocks/iface_mapper.go
@@ -10,8 +10,8 @@ import (
gomock "github.com/golang/mock/gomock"
wgdevice "golang.zx2c4.com/wireguard/device"
- iface "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// MockIFaceMapper is a mock of IFaceMapper interface.
@@ -38,10 +38,10 @@ func (m *MockIFaceMapper) EXPECT() *MockIFaceMapperMockRecorder {
}
// Address mocks base method.
-func (m *MockIFaceMapper) Address() iface.WGAddress {
+func (m *MockIFaceMapper) Address() wgaddr.Address {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Address")
- ret0, _ := ret[0].(iface.WGAddress)
+ ret0, _ := ret[0].(wgaddr.Address)
return ret0
}
diff --git a/client/internal/connect.go b/client/internal/connect.go
index bf513ed39..504c88c6f 100644
--- a/client/internal/connect.go
+++ b/client/internal/connect.go
@@ -61,7 +61,7 @@ func NewConnectClient(
}
// Run with main logic.
-func (c *ConnectClient) Run(runningChan chan error) error {
+func (c *ConnectClient) Run(runningChan chan struct{}) error {
return c.run(MobileDependency{}, runningChan)
}
@@ -102,7 +102,7 @@ func (c *ConnectClient) RunOniOS(
return c.run(mobileDependency, nil)
}
-func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan error) error {
+func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan struct{}) error {
defer func() {
if r := recover(); r != nil {
rec := c.statusRecorder
@@ -159,10 +159,9 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
}
defer c.statusRecorder.ClientStop()
- runningChanOpen := true
operation := func() error {
// if context cancelled we not start new backoff cycle
- if c.isContextCancelled() {
+ if c.ctx.Err() != nil {
return nil
}
@@ -282,10 +281,11 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
log.Infof("Netbird engine started, the IP is: %s", peerConfig.GetAddress())
state.Set(StatusConnected)
- if runningChan != nil && runningChanOpen {
- runningChan <- nil
- close(runningChan)
- runningChanOpen = false
+ if runningChan != nil {
+ select {
+ case runningChan <- struct{}{}:
+ default:
+ }
}
<-engineCtx.Done()
@@ -379,15 +379,6 @@ func (c *ConnectClient) Stop() error {
return nil
}
-func (c *ConnectClient) isContextCancelled() bool {
- select {
- case <-c.ctx.Done():
- return true
- default:
- return false
- }
-}
-
// SetNetworkMapPersistence enables or disables network map persistence.
// When enabled, the last received network map will be stored and can be retrieved
// through the Engine's getLatestNetworkMap method. When disabled, any stored
diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go
index 7c75f6bed..c7eeb7870 100644
--- a/client/internal/dns/server_test.go
+++ b/client/internal/dns/server_test.go
@@ -22,6 +22,7 @@ import (
"github.com/netbirdio/netbird/client/iface/configurer"
"github.com/netbirdio/netbird/client/iface/device"
pfmock "github.com/netbirdio/netbird/client/iface/mocks"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/netflow"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/statemanager"
@@ -30,7 +31,7 @@ import (
"github.com/netbirdio/netbird/formatter"
)
-var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}).GetLogger()
+var flowLogger = netflow.NewManager(context.Background(), nil, []byte{}, nil).GetLogger()
type mocWGIface struct {
filter device.PacketFilter
@@ -40,9 +41,9 @@ func (w *mocWGIface) Name() string {
panic("implement me")
}
-func (w *mocWGIface) Address() iface.WGAddress {
+func (w *mocWGIface) Address() wgaddr.Address {
ip, network, _ := net.ParseCIDR("100.66.100.0/24")
- return iface.WGAddress{
+ return wgaddr.Address{
IP: ip,
Network: network,
}
@@ -458,7 +459,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
}
packetfilter := pfmock.NewMockPacketFilter(ctrl)
- packetfilter.EXPECT().DropOutgoing(gomock.Any()).AnyTimes()
+ packetfilter.EXPECT().DropOutgoing(gomock.Any(), gomock.Any()).AnyTimes()
packetfilter.EXPECT().AddUDPPacketHook(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
packetfilter.EXPECT().RemovePacketHook(gomock.Any())
packetfilter.EXPECT().SetNetwork(ipNet)
@@ -1018,7 +1019,7 @@ func TestHandlerChain_DomainPriorities(t *testing.T) {
mh.AssertExpectations(t)
}
- // Reset mocks
+ // Close mocks
if mh, ok := tc.expectedHandler.(*MockHandler); ok {
mh.ExpectedCalls = nil
mh.Calls = nil
diff --git a/client/internal/dns/service_memory.go b/client/internal/dns/service_memory.go
index 250f3ab2e..34c563757 100644
--- a/client/internal/dns/service_memory.go
+++ b/client/internal/dns/service_memory.go
@@ -2,7 +2,7 @@ package dns
import (
"fmt"
- "net"
+ "net/netip"
"sync"
"github.com/google/gopacket"
@@ -117,5 +117,10 @@ func (s *ServiceViaMemory) filterDNSTraffic() (string, error) {
return true
}
- return filter.AddUDPPacketHook(false, net.ParseIP(s.runtimeIP), uint16(s.runtimePort), hook), nil
+ ip, err := netip.ParseAddr(s.runtimeIP)
+ if err != nil {
+ return "", fmt.Errorf("parse runtime ip: %w", err)
+ }
+
+ return filter.AddUDPPacketHook(false, ip, uint16(s.runtimePort), hook), nil
}
diff --git a/client/internal/dns/wgiface.go b/client/internal/dns/wgiface.go
index 69bc83659..c6c1752e5 100644
--- a/client/internal/dns/wgiface.go
+++ b/client/internal/dns/wgiface.go
@@ -5,15 +5,15 @@ package dns
import (
"net"
- "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/configurer"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// WGIface defines subset methods of interface required for manager
type WGIface interface {
Name() string
- Address() iface.WGAddress
+ Address() wgaddr.Address
ToInterface() *net.Interface
IsUserspaceBind() bool
GetFilter() device.PacketFilter
diff --git a/client/internal/dns/wgiface_windows.go b/client/internal/dns/wgiface_windows.go
index 765132fdb..74e5c75a5 100644
--- a/client/internal/dns/wgiface_windows.go
+++ b/client/internal/dns/wgiface_windows.go
@@ -1,15 +1,15 @@
package dns
import (
- "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/configurer"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
// WGIface defines subset methods of interface required for manager
type WGIface interface {
Name() string
- Address() iface.WGAddress
+ Address() wgaddr.Address
IsUserspaceBind() bool
GetFilter() device.PacketFilter
GetDevice() *device.FilteredDevice
diff --git a/client/internal/engine.go b/client/internal/engine.go
index 9e5d8a42d..5fc93c52e 100644
--- a/client/internal/engine.go
+++ b/client/internal/engine.go
@@ -353,7 +353,7 @@ func (e *Engine) Start() error {
// start flow manager right after interface creation
publicKey := e.config.WgPrivateKey.PublicKey()
- e.flowManager = netflow.NewManager(e.ctx, e.wgInterface, publicKey[:])
+ e.flowManager = netflow.NewManager(e.ctx, e.wgInterface, publicKey[:], e.statusRecorder)
if e.config.RosenpassEnabled {
log.Infof("rosenpass is enabled")
@@ -1428,7 +1428,7 @@ func (e *Engine) close() {
}
if e.firewall != nil {
- err := e.firewall.Reset(e.stateManager)
+ err := e.firewall.Close(e.stateManager)
if err != nil {
log.Warnf("failed to reset firewall: %s", err)
}
@@ -1641,16 +1641,19 @@ func (e *Engine) probeTURNs() []relay.ProbeResult {
return relay.ProbeAll(e.ctx, relay.ProbeTURN, turns)
}
+// restartEngine restarts the engine by cancelling the client context
func (e *Engine) restartEngine() {
- log.Info("restarting engine")
- CtxGetState(e.ctx).Set(StatusConnecting)
+ e.syncMsgMux.Lock()
+ defer e.syncMsgMux.Unlock()
- if err := e.Stop(); err != nil {
- log.Errorf("Failed to stop engine: %v", err)
+ if e.ctx.Err() != nil {
+ return
}
+ log.Info("restarting engine")
+ CtxGetState(e.ctx).Set(StatusConnecting)
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
- log.Infof("cancelling client, engine will be recreated")
+ log.Infof("cancelling client context, engine will be recreated")
e.clientCancel()
}
@@ -1662,34 +1665,17 @@ func (e *Engine) startNetworkMonitor() {
e.networkMonitor = networkmonitor.New()
go func() {
- var mu sync.Mutex
- var debounceTimer *time.Timer
-
- // Start the network monitor with a callback, Start will block until the monitor is stopped,
- // a network change is detected, or an error occurs on start up
- err := e.networkMonitor.Start(e.ctx, func() {
- // This function is called when a network change is detected
- mu.Lock()
- defer mu.Unlock()
-
- if debounceTimer != nil {
- log.Infof("Network monitor: detected network change, reset debounceTimer")
- debounceTimer.Stop()
+ if err := e.networkMonitor.Listen(e.ctx); err != nil {
+ if errors.Is(err, context.Canceled) {
+ log.Infof("network monitor stopped")
+ return
}
-
- // Set a new timer to debounce rapid network changes
- debounceTimer = time.AfterFunc(2*time.Second, func() {
- // This function is called after the debounce period
- mu.Lock()
- defer mu.Unlock()
-
- log.Infof("Network monitor: detected network change, restarting engine")
- e.restartEngine()
- })
- })
- if err != nil && !errors.Is(err, networkmonitor.ErrStopped) {
- log.Errorf("Network monitor: %v", err)
+ log.Errorf("network monitor error: %v", err)
+ return
}
+
+ log.Infof("Network monitor: detected network change, restarting engine")
+ e.restartEngine()
}()
}
diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go
index 60f07cbec..828823de8 100644
--- a/client/internal/engine_test.go
+++ b/client/internal/engine_test.go
@@ -31,6 +31,7 @@ import (
"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/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
"github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/peer"
@@ -75,7 +76,7 @@ type MockWGIface struct {
CreateOnAndroidFunc func(routeRange []string, ip string, domains []string) error
IsUserspaceBindFunc func() bool
NameFunc func() string
- AddressFunc func() device.WGAddress
+ AddressFunc func() wgaddr.Address
ToInterfaceFunc func() *net.Interface
UpFunc func() (*bind.UniversalUDPMuxDefault, error)
UpdateAddrFunc func(newAddr string) error
@@ -114,7 +115,7 @@ func (m *MockWGIface) Name() string {
return m.NameFunc()
}
-func (m *MockWGIface) Address() device.WGAddress {
+func (m *MockWGIface) Address() wgaddr.Address {
return m.AddressFunc()
}
@@ -364,8 +365,8 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
RemovePeerFunc: func(peerKey string) error {
return nil
},
- AddressFunc: func() iface.WGAddress {
- return iface.WGAddress{
+ AddressFunc: func() wgaddr.Address {
+ return wgaddr.Address{
IP: net.ParseIP("10.20.0.1"),
Network: &net.IPNet{
IP: net.ParseIP("10.20.0.0"),
diff --git a/client/internal/iface_common.go b/client/internal/iface_common.go
index 65b425015..ffeffaf41 100644
--- a/client/internal/iface_common.go
+++ b/client/internal/iface_common.go
@@ -12,6 +12,7 @@ import (
"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/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
@@ -20,7 +21,7 @@ type wgIfaceBase interface {
CreateOnAndroid(routeRange []string, ip string, domains []string) error
IsUserspaceBind() bool
Name() string
- Address() device.WGAddress
+ Address() wgaddr.Address
ToInterface() *net.Interface
Up() (*bind.UniversalUDPMuxDefault, error)
UpdateAddr(newAddr string) error
diff --git a/client/internal/netflow/logger/logger.go b/client/internal/netflow/logger/logger.go
index 1e23c1dce..7dde01d06 100644
--- a/client/internal/netflow/logger/logger.go
+++ b/client/internal/netflow/logger/logger.go
@@ -2,6 +2,7 @@ package logger
import (
"context"
+ "net"
"sync"
"sync/atomic"
"time"
@@ -11,6 +12,7 @@ import (
"github.com/netbirdio/netbird/client/internal/netflow/store"
"github.com/netbirdio/netbird/client/internal/netflow/types"
+ "github.com/netbirdio/netbird/client/internal/peer"
)
type rcvChan chan *types.EventFields
@@ -21,15 +23,20 @@ type Logger struct {
enabled atomic.Bool
rcvChan atomic.Pointer[rcvChan]
cancelReceiver context.CancelFunc
+ statusRecorder *peer.Status
+ wgIfaceIPNet net.IPNet
Store types.Store
}
-func New(ctx context.Context) *Logger {
+func New(ctx context.Context, statusRecorder *peer.Status, wgIfaceIPNet net.IPNet) *Logger {
+
ctx, cancel := context.WithCancel(ctx)
return &Logger{
- ctx: ctx,
- cancel: cancel,
- Store: store.NewMemoryStore(),
+ ctx: ctx,
+ cancel: cancel,
+ statusRecorder: statusRecorder,
+ wgIfaceIPNet: wgIfaceIPNet,
+ Store: store.NewMemoryStore(),
}
}
@@ -58,13 +65,14 @@ func (l *Logger) startReceiver() {
if l.enabled.Load() {
return
}
+
l.mux.Lock()
ctx, cancel := context.WithCancel(l.ctx)
l.cancelReceiver = cancel
l.mux.Unlock()
c := make(rcvChan, 100)
- l.rcvChan.Swap(&c)
+ l.rcvChan.Store(&c)
l.enabled.Store(true)
for {
@@ -73,12 +81,23 @@ func (l *Logger) startReceiver() {
log.Info("flow Memory store receiver stopped")
return
case eventFields := <-c:
- id := uuid.NewString()
+ id := uuid.New()
event := types.Event{
ID: id,
EventFields: *eventFields,
Timestamp: time.Now(),
}
+
+ if event.Direction == types.Ingress {
+ if !l.wgIfaceIPNet.Contains(net.IP(event.SourceIP.AsSlice())) {
+ event.SourceResourceID = []byte(l.statusRecorder.CheckRoutes(event.SourceIP))
+ }
+ } else if event.Direction == types.Egress {
+ if !l.wgIfaceIPNet.Contains(net.IP(event.DestIP.AsSlice())) {
+ event.DestResourceID = []byte(l.statusRecorder.CheckRoutes(event.DestIP))
+ }
+ }
+
l.Store.StoreEvent(&event)
}
}
@@ -100,6 +119,7 @@ func (l *Logger) stop() {
l.cancelReceiver()
l.cancelReceiver = nil
}
+ l.rcvChan.Store(nil)
l.mux.Unlock()
}
@@ -107,7 +127,7 @@ func (l *Logger) GetEvents() []*types.Event {
return l.Store.GetEvents()
}
-func (l *Logger) DeleteEvents(ids []string) {
+func (l *Logger) DeleteEvents(ids []uuid.UUID) {
l.Store.DeleteEvents(ids)
}
diff --git a/client/internal/netflow/logger/logger_test.go b/client/internal/netflow/logger/logger_test.go
index e986118ec..3ce9d8fd8 100644
--- a/client/internal/netflow/logger/logger_test.go
+++ b/client/internal/netflow/logger/logger_test.go
@@ -2,6 +2,7 @@ package logger_test
import (
"context"
+ "net"
"testing"
"time"
@@ -12,7 +13,7 @@ import (
)
func TestStore(t *testing.T) {
- logger := logger.New(context.Background())
+ logger := logger.New(context.Background(), nil, net.IPNet{})
logger.Enable()
event := types.EventFields{
diff --git a/client/internal/netflow/manager.go b/client/internal/netflow/manager.go
index 8ab81f4ff..6ef1f4e7d 100644
--- a/client/internal/netflow/manager.go
+++ b/client/internal/netflow/manager.go
@@ -2,17 +2,21 @@ package netflow
import (
"context"
+ "errors"
"fmt"
+ "net"
"runtime"
"sync"
"time"
+ "github.com/google/uuid"
log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/netbirdio/netbird/client/internal/netflow/conntrack"
"github.com/netbirdio/netbird/client/internal/netflow/logger"
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
+ "github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/flow/client"
"github.com/netbirdio/netbird/flow/proto"
)
@@ -29,8 +33,12 @@ type Manager struct {
}
// NewManager creates a new netflow manager
-func NewManager(ctx context.Context, iface nftypes.IFaceMapper, publicKey []byte) *Manager {
- flowLogger := logger.New(ctx)
+func NewManager(ctx context.Context, iface nftypes.IFaceMapper, publicKey []byte, statusRecorder *peer.Status) *Manager {
+ var ipNet net.IPNet
+ if iface != nil {
+ ipNet = *iface.Address().Network
+ }
+ flowLogger := logger.New(ctx, statusRecorder, ipNet)
var ct nftypes.ConnTracker
if runtime.GOOS == "linux" && iface != nil && !iface.IsUserspaceBind() {
@@ -45,46 +53,80 @@ func NewManager(ctx context.Context, iface nftypes.IFaceMapper, publicKey []byte
}
}
+// Update applies new flow configuration settings
+// needsNewClient checks if a new client needs to be created
+func (m *Manager) needsNewClient(previous *nftypes.FlowConfig) bool {
+ current := m.flowConfig
+ return previous == nil ||
+ !previous.Enabled ||
+ previous.TokenPayload != current.TokenPayload ||
+ previous.TokenSignature != current.TokenSignature ||
+ previous.URL != current.URL
+}
+
+// enableFlow starts components for flow tracking
+func (m *Manager) enableFlow(previous *nftypes.FlowConfig) error {
+ // first make sender ready so events don't pile up
+ if m.needsNewClient(previous) {
+ if m.receiverClient != nil {
+ if err := m.receiverClient.Close(); err != nil {
+ log.Warnf("error closing previous flow client: %s", err)
+ }
+ }
+
+ flowClient, err := client.NewClient(m.flowConfig.URL, m.flowConfig.TokenPayload, m.flowConfig.TokenSignature, m.flowConfig.Interval)
+ if err != nil {
+ return fmt.Errorf("create client: %w", err)
+ }
+ log.Infof("flow client configured to connect to %s", m.flowConfig.URL)
+
+ m.receiverClient = flowClient
+ go m.receiveACKs(flowClient)
+ go m.startSender()
+ }
+
+ m.logger.Enable()
+
+ if m.conntrack != nil {
+ if err := m.conntrack.Start(m.flowConfig.Counters); err != nil {
+ return fmt.Errorf("start conntrack: %w", err)
+ }
+ }
+
+ return nil
+}
+
+// disableFlow stops components for flow tracking
+func (m *Manager) disableFlow() error {
+ if m.conntrack != nil {
+ m.conntrack.Stop()
+ }
+
+ m.logger.Disable()
+
+ if m.receiverClient != nil {
+ return m.receiverClient.Close()
+ }
+ return nil
+}
+
// Update applies new flow configuration settings
func (m *Manager) Update(update *nftypes.FlowConfig) error {
if update == nil {
return nil
}
+
m.mux.Lock()
defer m.mux.Unlock()
+
previous := m.flowConfig
m.flowConfig = update
if update.Enabled {
- if m.conntrack != nil {
- if err := m.conntrack.Start(update.Counters); err != nil {
- return fmt.Errorf("start conntrack: %w", err)
- }
- }
-
- m.logger.Enable()
- if previous == nil || !previous.Enabled {
- flowClient, err := client.NewClient(m.ctx, m.flowConfig.URL, m.flowConfig.TokenPayload, m.flowConfig.TokenSignature)
- if err != nil {
- return err
- }
- log.Infof("flow client connected to %s", m.flowConfig.URL)
- m.receiverClient = flowClient
- go m.receiveACKs()
- go m.startSender()
- }
- return nil
+ return m.enableFlow(previous)
}
- if m.conntrack != nil {
- m.conntrack.Stop()
- }
- m.logger.Disable()
- if previous != nil && previous.Enabled {
- return m.receiverClient.Close()
- }
-
- return nil
+ return m.disableFlow()
}
// Close cleans up all resources
@@ -95,6 +137,13 @@ func (m *Manager) Close() {
if m.conntrack != nil {
m.conntrack.Close()
}
+
+ if m.receiverClient != nil {
+ if err := m.receiverClient.Close(); err != nil {
+ log.Warnf("failed to close receiver client: %s", err)
+ }
+ }
+
m.logger.Close()
}
@@ -106,6 +155,7 @@ func (m *Manager) GetLogger() nftypes.FlowLogger {
func (m *Manager) startSender() {
ticker := time.NewTicker(m.flowConfig.Interval)
defer ticker.Stop()
+
for {
select {
case <-m.ctx.Done():
@@ -113,56 +163,62 @@ func (m *Manager) startSender() {
case <-ticker.C:
events := m.logger.GetEvents()
for _, event := range events {
- log.Infof("send flow event to server: %s", event.ID)
- err := m.send(event)
- if err != nil {
- log.Errorf("send flow event to server: %s", err)
+ if err := m.send(event); err != nil {
+ log.Errorf("failed to send flow event to server: %s", err)
+ continue
}
+ log.Tracef("sent flow event: %s", event.ID)
}
}
}
}
-func (m *Manager) receiveACKs() {
- if m.receiverClient == nil {
- return
- }
- err := m.receiverClient.Receive(m.ctx, func(ack *proto.FlowEventAck) error {
- log.Infof("receive flow event ack: %s", ack.EventId)
- m.logger.DeleteEvents([]string{ack.EventId})
+func (m *Manager) receiveACKs(client *client.GRPCClient) {
+ err := client.Receive(m.ctx, m.flowConfig.Interval, func(ack *proto.FlowEventAck) error {
+ log.Tracef("received flow event ack: %s", ack.EventId)
+ m.logger.DeleteEvents([]uuid.UUID{uuid.UUID(ack.EventId)})
return nil
})
- if err != nil {
- log.Errorf("receive flow event ack: %s", err)
+
+ if err != nil && !errors.Is(err, context.Canceled) {
+ log.Errorf("failed to receive flow event ack: %s", err)
}
}
func (m *Manager) send(event *nftypes.Event) error {
- if m.receiverClient == nil {
+ m.mux.Lock()
+ client := m.receiverClient
+ m.mux.Unlock()
+
+ if client == nil {
return nil
}
- return m.receiverClient.Send(m.ctx, toProtoEvent(m.publicKey, event))
+
+ return client.Send(toProtoEvent(m.publicKey, event))
}
func toProtoEvent(publicKey []byte, event *nftypes.Event) *proto.FlowEvent {
protoEvent := &proto.FlowEvent{
- EventId: event.ID,
+ EventId: event.ID[:],
Timestamp: timestamppb.New(event.Timestamp),
PublicKey: publicKey,
FlowFields: &proto.FlowFields{
- FlowId: event.FlowID[:],
- RuleId: event.RuleID,
- Type: proto.Type(event.Type),
- Direction: proto.Direction(event.Direction),
- Protocol: uint32(event.Protocol),
- SourceIp: event.SourceIP.AsSlice(),
- DestIp: event.DestIP.AsSlice(),
- RxPackets: event.RxPackets,
- TxPackets: event.TxPackets,
- RxBytes: event.RxBytes,
- TxBytes: event.TxBytes,
+ FlowId: event.FlowID[:],
+ RuleId: event.RuleID,
+ Type: proto.Type(event.Type),
+ Direction: proto.Direction(event.Direction),
+ Protocol: uint32(event.Protocol),
+ SourceIp: event.SourceIP.AsSlice(),
+ DestIp: event.DestIP.AsSlice(),
+ RxPackets: event.RxPackets,
+ TxPackets: event.TxPackets,
+ RxBytes: event.RxBytes,
+ TxBytes: event.TxBytes,
+ SourceResourceId: event.SourceResourceID,
+ DestResourceId: event.DestResourceID,
},
}
+
if event.Protocol == nftypes.ICMP {
protoEvent.FlowFields.ConnectionInfo = &proto.FlowFields_IcmpInfo{
IcmpInfo: &proto.ICMPInfo{
diff --git a/client/internal/netflow/store/memory.go b/client/internal/netflow/store/memory.go
index b0dcbd6f8..b695a0a12 100644
--- a/client/internal/netflow/store/memory.go
+++ b/client/internal/netflow/store/memory.go
@@ -3,18 +3,22 @@ package store
import (
"sync"
+ "golang.org/x/exp/maps"
+
+ "github.com/google/uuid"
+
"github.com/netbirdio/netbird/client/internal/netflow/types"
)
func NewMemoryStore() *Memory {
return &Memory{
- events: make(map[string]*types.Event),
+ events: make(map[uuid.UUID]*types.Event),
}
}
type Memory struct {
mux sync.Mutex
- events map[string]*types.Event
+ events map[uuid.UUID]*types.Event
}
func (m *Memory) StoreEvent(event *types.Event) {
@@ -26,7 +30,7 @@ func (m *Memory) StoreEvent(event *types.Event) {
func (m *Memory) Close() {
m.mux.Lock()
defer m.mux.Unlock()
- m.events = make(map[string]*types.Event)
+ maps.Clear(m.events)
}
func (m *Memory) GetEvents() []*types.Event {
@@ -39,7 +43,7 @@ func (m *Memory) GetEvents() []*types.Event {
return events
}
-func (m *Memory) DeleteEvents(ids []string) {
+func (m *Memory) DeleteEvents(ids []uuid.UUID) {
m.mux.Lock()
defer m.mux.Unlock()
for _, id := range ids {
diff --git a/client/internal/netflow/types/types.go b/client/internal/netflow/types/types.go
index a0f2eb95e..2787b6284 100644
--- a/client/internal/netflow/types/types.go
+++ b/client/internal/netflow/types/types.go
@@ -2,11 +2,12 @@ package types
import (
"net/netip"
+ "strconv"
"time"
"github.com/google/uuid"
- "github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type Protocol uint8
@@ -27,8 +28,10 @@ func (p Protocol) String() string {
return "TCP"
case 17:
return "UDP"
+ case 132:
+ return "SCTP"
default:
- return "unknown"
+ return strconv.FormatUint(uint64(p), 10)
}
}
@@ -61,27 +64,29 @@ const (
)
type Event struct {
- ID string
+ ID uuid.UUID
Timestamp time.Time
EventFields
}
type EventFields struct {
- FlowID uuid.UUID
- Type Type
- RuleID []byte
- Direction Direction
- Protocol Protocol
- SourceIP netip.Addr
- DestIP netip.Addr
- SourcePort uint16
- DestPort uint16
- ICMPType uint8
- ICMPCode uint8
- RxPackets uint64
- TxPackets uint64
- RxBytes uint64
- TxBytes uint64
+ FlowID uuid.UUID
+ Type Type
+ RuleID []byte
+ Direction Direction
+ Protocol Protocol
+ SourceIP netip.Addr
+ DestIP netip.Addr
+ SourceResourceID []byte
+ DestResourceID []byte
+ SourcePort uint16
+ DestPort uint16
+ ICMPType uint8
+ ICMPCode uint8
+ RxPackets uint64
+ TxPackets uint64
+ RxBytes uint64
+ TxBytes uint64
}
type FlowConfig struct {
@@ -108,7 +113,7 @@ type FlowLogger interface {
// GetEvents returns all stored events
GetEvents() []*Event
// DeleteEvents deletes events from the store
- DeleteEvents([]string)
+ DeleteEvents([]uuid.UUID)
// Close closes the logger
Close()
// Enable enables the flow logger receiver
@@ -123,7 +128,7 @@ type Store interface {
// GetEvents returns all stored events
GetEvents() []*Event
// DeleteEvents deletes events from the store
- DeleteEvents([]string)
+ DeleteEvents([]uuid.UUID)
// Close closes the store
Close()
}
@@ -142,5 +147,5 @@ type ConnTracker interface {
type IFaceMapper interface {
IsUserspaceBind() bool
Name() string
- Address() device.WGAddress
+ Address() wgaddr.Address
}
diff --git a/client/internal/networkmonitor/monitor_bsd.go b/client/internal/networkmonitor/check_change_bsd.go
similarity index 90%
rename from client/internal/networkmonitor/monitor_bsd.go
rename to client/internal/networkmonitor/check_change_bsd.go
index 4dc2c1aa3..bb327a877 100644
--- a/client/internal/networkmonitor/monitor_bsd.go
+++ b/client/internal/networkmonitor/check_change_bsd.go
@@ -16,7 +16,7 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
)
-func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, callback func()) error {
+func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) error {
fd, err := unix.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
if err != nil {
return fmt.Errorf("failed to open routing socket: %v", err)
@@ -28,18 +28,10 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
}
}()
- go func() {
- <-ctx.Done()
- err := unix.Close(fd)
- if err != nil && !errors.Is(err, unix.EBADF) {
- log.Debugf("Network monitor: closed routing socket: %v", err)
- }
- }()
-
for {
select {
case <-ctx.Done():
- return ErrStopped
+ return ctx.Err()
default:
buf := make([]byte, 2048)
n, err := unix.Read(fd, buf)
@@ -76,11 +68,11 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
switch msg.Type {
case unix.RTM_ADD:
log.Infof("Network monitor: default route changed: via %s, interface %s", route.Gw, intf)
- go callback()
+ return nil
case unix.RTM_DELETE:
if nexthopv4.Intf != nil && route.Gw.Compare(nexthopv4.IP) == 0 || nexthopv6.Intf != nil && route.Gw.Compare(nexthopv6.IP) == 0 {
log.Infof("Network monitor: default route removed: via %s, interface %s", route.Gw, intf)
- go callback()
+ return nil
}
}
}
diff --git a/client/internal/networkmonitor/monitor_linux.go b/client/internal/networkmonitor/check_change_linux.go
similarity index 93%
rename from client/internal/networkmonitor/monitor_linux.go
rename to client/internal/networkmonitor/check_change_linux.go
index 035be1f09..efd8b5884 100644
--- a/client/internal/networkmonitor/monitor_linux.go
+++ b/client/internal/networkmonitor/check_change_linux.go
@@ -14,7 +14,7 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
)
-func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, callback func()) error {
+func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) error {
if nexthopv4.Intf == nil && nexthopv6.Intf == nil {
return errors.New("no interfaces available")
}
@@ -31,8 +31,7 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
for {
select {
case <-ctx.Done():
- return ErrStopped
-
+ return ctx.Err()
// handle route changes
case route := <-routeChan:
// default route and main table
@@ -43,12 +42,10 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
// triggered on added/replaced routes
case syscall.RTM_NEWROUTE:
log.Infof("Network monitor: default route changed: via %s, interface %d", route.Gw, route.LinkIndex)
- go callback()
return nil
case syscall.RTM_DELROUTE:
if nexthopv4.Intf != nil && route.Gw.Equal(nexthopv4.IP.AsSlice()) || nexthopv6.Intf != nil && route.Gw.Equal(nexthopv6.IP.AsSlice()) {
log.Infof("Network monitor: default route removed: via %s, interface %d", route.Gw, route.LinkIndex)
- go callback()
return nil
}
}
diff --git a/client/internal/networkmonitor/monitor_windows.go b/client/internal/networkmonitor/check_change_windows.go
similarity index 89%
rename from client/internal/networkmonitor/monitor_windows.go
rename to client/internal/networkmonitor/check_change_windows.go
index cd48c269d..582865738 100644
--- a/client/internal/networkmonitor/monitor_windows.go
+++ b/client/internal/networkmonitor/check_change_windows.go
@@ -10,7 +10,7 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
)
-func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, callback func()) error {
+func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) error {
routeMonitor, err := systemops.NewRouteMonitor(ctx)
if err != nil {
return fmt.Errorf("failed to create route monitor: %w", err)
@@ -24,20 +24,20 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
for {
select {
case <-ctx.Done():
- return ErrStopped
+ return ctx.Err()
case route := <-routeMonitor.RouteUpdates():
if route.Destination.Bits() != 0 {
continue
}
- if routeChanged(route, nexthopv4, nexthopv6, callback) {
- break
+ if routeChanged(route, nexthopv4, nexthopv6) {
+ return nil
}
}
}
}
-func routeChanged(route systemops.RouteUpdate, nexthopv4, nexthopv6 systemops.Nexthop, callback func()) bool {
+func routeChanged(route systemops.RouteUpdate, nexthopv4, nexthopv6 systemops.Nexthop) bool {
intf := ""
if route.Interface != nil {
intf = route.Interface.Name
@@ -51,18 +51,15 @@ func routeChanged(route systemops.RouteUpdate, nexthopv4, nexthopv6 systemops.Ne
case systemops.RouteModified:
// TODO: get routing table to figure out if our route is affected for modified routes
log.Infof("Network monitor: default route changed: via %s, interface %s", route.NextHop, intf)
- go callback()
return true
case systemops.RouteAdded:
if route.NextHop.Is4() && route.NextHop != nexthopv4.IP || route.NextHop.Is6() && route.NextHop != nexthopv6.IP {
log.Infof("Network monitor: default route added: via %s, interface %s", route.NextHop, intf)
- go callback()
return true
}
case systemops.RouteDeleted:
if nexthopv4.Intf != nil && route.NextHop == nexthopv4.IP || nexthopv6.Intf != nil && route.NextHop == nexthopv6.IP {
log.Infof("Network monitor: default route removed: via %s, interface %s", route.NextHop, intf)
- go callback()
return true
}
}
diff --git a/client/internal/networkmonitor/monitor.go b/client/internal/networkmonitor/monitor.go
index 5475455c6..5896b66b6 100644
--- a/client/internal/networkmonitor/monitor.go
+++ b/client/internal/networkmonitor/monitor.go
@@ -1,12 +1,27 @@
+//go:build !ios && !android
+
package networkmonitor
import (
"context"
"errors"
+ "fmt"
+ "net/netip"
+ "runtime/debug"
"sync"
+ "time"
+
+ "github.com/cenkalti/backoff/v4"
+ log "github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/client/internal/routemanager/systemops"
)
-var ErrStopped = errors.New("monitor has been stopped")
+const (
+ debounceTime = 2 * time.Second
+)
+
+var checkChangeFn = checkChange
// NetworkMonitor watches for changes in network configuration.
type NetworkMonitor struct {
@@ -19,3 +34,99 @@ type NetworkMonitor struct {
func New() *NetworkMonitor {
return &NetworkMonitor{}
}
+
+// Listen begins monitoring network changes. When a change is detected, this function will return without error.
+func (nw *NetworkMonitor) Listen(ctx context.Context) (err error) {
+ nw.mu.Lock()
+ if nw.cancel != nil {
+ nw.mu.Unlock()
+ return errors.New("network monitor already started")
+ }
+
+ ctx, nw.cancel = context.WithCancel(ctx)
+ defer nw.cancel()
+ nw.wg.Add(1)
+ nw.mu.Unlock()
+
+ defer nw.wg.Done()
+
+ var nexthop4, nexthop6 systemops.Nexthop
+
+ operation := func() error {
+ var errv4, errv6 error
+ nexthop4, errv4 = systemops.GetNextHop(netip.IPv4Unspecified())
+ nexthop6, errv6 = systemops.GetNextHop(netip.IPv6Unspecified())
+
+ if errv4 != nil && errv6 != nil {
+ return errors.New("failed to get default next hops")
+ }
+
+ if errv4 == nil {
+ log.Debugf("Network monitor: IPv4 default route: %s, interface: %s", nexthop4.IP, nexthop4.Intf.Name)
+ }
+ if errv6 == nil {
+ log.Debugf("Network monitor: IPv6 default route: %s, interface: %s", nexthop6.IP, nexthop6.Intf.Name)
+ }
+
+ // continue if either route was found
+ return nil
+ }
+
+ expBackOff := backoff.WithContext(backoff.NewExponentialBackOff(), ctx)
+
+ if err := backoff.Retry(operation, expBackOff); err != nil {
+ return fmt.Errorf("failed to get default next hops: %w", err)
+ }
+
+ // recover in case sys ops panic
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("panic occurred: %v, stack trace: %s", r, debug.Stack())
+ }
+ }()
+
+ event := make(chan struct{}, 1)
+ go nw.checkChanges(ctx, event, nexthop4, nexthop6)
+
+ // debounce changes
+ timer := time.NewTimer(0)
+ timer.Stop()
+ for {
+ select {
+ case <-event:
+ timer.Reset(debounceTime)
+ case <-timer.C:
+ return nil
+ case <-ctx.Done():
+ timer.Stop()
+ return ctx.Err()
+ }
+ }
+}
+
+// Stop stops the network monitor.
+func (nw *NetworkMonitor) Stop() {
+ nw.mu.Lock()
+ defer nw.mu.Unlock()
+
+ if nw.cancel == nil {
+ return
+ }
+
+ nw.cancel()
+ nw.wg.Wait()
+}
+
+func (nw *NetworkMonitor) checkChanges(ctx context.Context, event chan struct{}, nexthop4 systemops.Nexthop, nexthop6 systemops.Nexthop) {
+ for {
+ if err := checkChangeFn(ctx, nexthop4, nexthop6); err != nil {
+ close(event)
+ return
+ }
+ // prevent blocking
+ select {
+ case event <- struct{}{}:
+ default:
+ }
+ }
+}
diff --git a/client/internal/networkmonitor/monitor_generic.go b/client/internal/networkmonitor/monitor_generic.go
deleted file mode 100644
index 19648edba..000000000
--- a/client/internal/networkmonitor/monitor_generic.go
+++ /dev/null
@@ -1,82 +0,0 @@
-//go:build !ios && !android
-
-package networkmonitor
-
-import (
- "context"
- "errors"
- "fmt"
- "net/netip"
- "runtime/debug"
-
- "github.com/cenkalti/backoff/v4"
- log "github.com/sirupsen/logrus"
-
- "github.com/netbirdio/netbird/client/internal/routemanager/systemops"
-)
-
-// Start begins monitoring network changes. When a change is detected, it calls the callback asynchronously and returns.
-func (nw *NetworkMonitor) Start(ctx context.Context, callback func()) (err error) {
- if ctx.Err() != nil {
- return ctx.Err()
- }
-
- nw.mu.Lock()
- ctx, nw.cancel = context.WithCancel(ctx)
- nw.mu.Unlock()
-
- nw.wg.Add(1)
- defer nw.wg.Done()
-
- var nexthop4, nexthop6 systemops.Nexthop
-
- operation := func() error {
- var errv4, errv6 error
- nexthop4, errv4 = systemops.GetNextHop(netip.IPv4Unspecified())
- nexthop6, errv6 = systemops.GetNextHop(netip.IPv6Unspecified())
-
- if errv4 != nil && errv6 != nil {
- return errors.New("failed to get default next hops")
- }
-
- if errv4 == nil {
- log.Debugf("Network monitor: IPv4 default route: %s, interface: %s", nexthop4.IP, nexthop4.Intf.Name)
- }
- if errv6 == nil {
- log.Debugf("Network monitor: IPv6 default route: %s, interface: %s", nexthop6.IP, nexthop6.Intf.Name)
- }
-
- // continue if either route was found
- return nil
- }
-
- expBackOff := backoff.WithContext(backoff.NewExponentialBackOff(), ctx)
-
- if err := backoff.Retry(operation, expBackOff); err != nil {
- return fmt.Errorf("failed to get default next hops: %w", err)
- }
-
- // recover in case sys ops panic
- defer func() {
- if r := recover(); r != nil {
- err = fmt.Errorf("panic occurred: %v, stack trace: %s", r, debug.Stack())
- }
- }()
-
- if err := checkChange(ctx, nexthop4, nexthop6, callback); err != nil {
- return fmt.Errorf("check change: %w", err)
- }
-
- return nil
-}
-
-// Stop stops the network monitor.
-func (nw *NetworkMonitor) Stop() {
- nw.mu.Lock()
- defer nw.mu.Unlock()
-
- if nw.cancel != nil {
- nw.cancel()
- nw.wg.Wait()
- }
-}
diff --git a/client/internal/networkmonitor/monitor_mobile.go b/client/internal/networkmonitor/monitor_mobile.go
index c81fad16c..861dbbe3c 100644
--- a/client/internal/networkmonitor/monitor_mobile.go
+++ b/client/internal/networkmonitor/monitor_mobile.go
@@ -2,10 +2,21 @@
package networkmonitor
-import "context"
+import (
+ "context"
+ "fmt"
+)
-func (nw *NetworkMonitor) Start(context.Context, func()) error {
- return nil
+type NetworkMonitor struct {
+}
+
+// New creates a new network monitor.
+func New() *NetworkMonitor {
+ return &NetworkMonitor{}
+}
+
+func (nw *NetworkMonitor) Listen(_ context.Context) error {
+ return fmt.Errorf("network monitor not supported on mobile platforms")
}
func (nw *NetworkMonitor) Stop() {
diff --git a/client/internal/networkmonitor/monitor_test.go b/client/internal/networkmonitor/monitor_test.go
new file mode 100644
index 000000000..164686689
--- /dev/null
+++ b/client/internal/networkmonitor/monitor_test.go
@@ -0,0 +1,99 @@
+package networkmonitor
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/netbirdio/netbird/client/internal/routemanager/systemops"
+)
+
+type MocMultiEvent struct {
+ counter int
+}
+
+func (m *MocMultiEvent) checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) error {
+ if m.counter == 0 {
+ <-ctx.Done()
+ return ctx.Err()
+ }
+
+ time.Sleep(1 * time.Second)
+ m.counter--
+ return nil
+}
+
+func TestNetworkMonitor_Close(t *testing.T) {
+ checkChangeFn = func(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) error {
+ <-ctx.Done()
+ return ctx.Err()
+ }
+ nw := New()
+
+ var resErr error
+ done := make(chan struct{})
+ go func() {
+ resErr = nw.Listen(context.Background())
+ close(done)
+ }()
+
+ time.Sleep(1 * time.Second) // wait for the goroutine to start
+ nw.Stop()
+
+ <-done
+ if !errors.Is(resErr, context.Canceled) {
+ t.Errorf("unexpected error: %v", resErr)
+ }
+}
+
+func TestNetworkMonitor_Event(t *testing.T) {
+ checkChangeFn = func(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) error {
+ timeout, cancel := context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-timeout.Done():
+ return nil
+ }
+ }
+ nw := New()
+ defer nw.Stop()
+
+ var resErr error
+ done := make(chan struct{})
+ go func() {
+ resErr = nw.Listen(context.Background())
+ close(done)
+ }()
+
+ <-done
+ if !errors.Is(resErr, nil) {
+ t.Errorf("unexpected error: %v", nil)
+ }
+}
+
+func TestNetworkMonitor_MultiEvent(t *testing.T) {
+ eventsRepeated := 3
+ me := &MocMultiEvent{counter: eventsRepeated}
+ checkChangeFn = me.checkChange
+
+ nw := New()
+ defer nw.Stop()
+
+ done := make(chan struct{})
+ started := time.Now()
+ go func() {
+ if resErr := nw.Listen(context.Background()); resErr != nil {
+ t.Errorf("unexpected error: %v", resErr)
+ }
+ close(done)
+ }()
+
+ <-done
+ expectedResponseTime := time.Duration(eventsRepeated)*time.Second + debounceTime
+ if time.Since(started) < expectedResponseTime {
+ t.Errorf("unexpected duration: %v", time.Since(started))
+ }
+}
diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go
index 9b4d1a554..b91cfe33c 100644
--- a/client/internal/peer/conn.go
+++ b/client/internal/peer/conn.go
@@ -442,8 +442,8 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
conn.log.Infof("created new wgProxy for relay connection: %s", wgProxy.EndpointAddr().String())
- if conn.iceP2PIsActive() {
- conn.log.Debugf("do not switch to relay because current priority is: %s", conn.currentConnPriority.String())
+ if conn.isICEActive() {
+ conn.log.Infof("do not switch to relay because current priority is: %s", conn.currentConnPriority.String())
conn.setRelayedProxy(wgProxy)
conn.statusRelay.Set(StatusConnected)
conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey)
@@ -711,8 +711,8 @@ func (conn *Conn) isReadyToUpgrade() bool {
return conn.wgProxyRelay != nil && conn.currentConnPriority != connPriorityRelay
}
-func (conn *Conn) iceP2PIsActive() bool {
- return conn.currentConnPriority == connPriorityICEP2P && conn.statusICE.Get() == StatusConnected
+func (conn *Conn) isICEActive() bool {
+ return (conn.currentConnPriority == connPriorityICEP2P || conn.currentConnPriority == connPriorityICETurn) && conn.statusICE.Get() == StatusConnected
}
func (conn *Conn) removeWgPeer() error {
diff --git a/client/internal/peer/iface.go b/client/internal/peer/iface.go
index c7b6de9ea..32ac5c7db 100644
--- a/client/internal/peer/iface.go
+++ b/client/internal/peer/iface.go
@@ -8,6 +8,7 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"github.com/netbirdio/netbird/client/iface/configurer"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface/wgproxy"
)
@@ -16,4 +17,5 @@ type WGIface interface {
RemovePeer(peerKey string) error
GetStats(peerKey string) (configurer.WGStats, error)
GetProxy() wgproxy.Proxy
+ Address() wgaddr.Address
}
diff --git a/client/internal/peer/route.go b/client/internal/peer/route.go
new file mode 100644
index 000000000..926d7c442
--- /dev/null
+++ b/client/internal/peer/route.go
@@ -0,0 +1,73 @@
+package peer
+
+import (
+ "net/netip"
+ "sync"
+
+ log "github.com/sirupsen/logrus"
+)
+
+type routeIDLookup struct {
+ localMap sync.Map
+ remoteMap sync.Map
+ resolvedIPs sync.Map
+}
+
+func (r *routeIDLookup) AddLocalRouteID(resourceID string, route netip.Prefix) {
+ _, exists := r.localMap.LoadOrStore(route, resourceID)
+ if exists {
+ log.Tracef("resourceID %s already exists in local map", resourceID)
+ }
+}
+
+func (r *routeIDLookup) RemoveLocalRouteID(route netip.Prefix) {
+ r.localMap.Delete(route)
+}
+
+func (r *routeIDLookup) AddRemoteRouteID(resourceID string, route netip.Prefix) {
+ _, exists := r.remoteMap.LoadOrStore(route, resourceID)
+ if exists {
+ log.Tracef("resourceID %s already exists in remote map", resourceID)
+ }
+}
+
+func (r *routeIDLookup) RemoveRemoteRouteID(route netip.Prefix) {
+ r.remoteMap.Delete(route)
+}
+
+func (r *routeIDLookup) AddResolvedIP(resourceID string, route netip.Prefix) {
+ r.resolvedIPs.Store(route.Addr(), resourceID)
+}
+
+func (r *routeIDLookup) RemoveResolvedIP(route netip.Prefix) {
+ r.resolvedIPs.Delete(route.Addr())
+}
+
+func (r *routeIDLookup) Lookup(ip netip.Addr) string {
+ resId, ok := r.resolvedIPs.Load(ip)
+ if ok {
+ return resId.(string)
+ }
+
+ var resourceID string
+ r.localMap.Range(func(key, value interface{}) bool {
+ if key.(netip.Prefix).Contains(ip) {
+ resourceID = value.(string)
+ return false
+
+ }
+ return true
+ })
+
+ if resourceID == "" {
+ r.remoteMap.Range(func(key, value interface{}) bool {
+ if key.(netip.Prefix).Contains(ip) {
+ resourceID = value.(string)
+ return false
+ }
+ return true
+ })
+ }
+
+ return resourceID
+}
diff --git a/client/internal/peer/status.go b/client/internal/peer/status.go
index ee884a76e..b6be827f8 100644
--- a/client/internal/peer/status.go
+++ b/client/internal/peer/status.go
@@ -176,6 +176,8 @@ type Status struct {
eventQueue *EventQueue
ingressGwMgr *ingressgw.Manager
+
+ routeIDLookup routeIDLookup
}
// NewRecorder returns a new Status instance
@@ -311,7 +313,7 @@ func (d *Status) UpdatePeerState(receivedState State) error {
return nil
}
-func (d *Status) AddPeerStateRoute(peer string, route string) error {
+func (d *Status) AddPeerStateRoute(peer string, route string, resourceId string) error {
d.mux.Lock()
defer d.mux.Unlock()
@@ -323,6 +325,14 @@ func (d *Status) AddPeerStateRoute(peer string, route string) error {
peerState.AddRoute(route)
d.peers[peer] = peerState
+ pref, err := netip.ParsePrefix(route)
+ if err != nil {
+ log.Errorf("failed to parse prefix %s: %v", route, err)
+ } else {
+
+ d.routeIDLookup.AddRemoteRouteID(resourceId, pref)
+ }
+
// todo: consider to make sense of this notification or not
d.notifyPeerListChanged()
return nil
@@ -340,11 +350,27 @@ func (d *Status) RemovePeerStateRoute(peer string, route string) error {
peerState.DeleteRoute(route)
d.peers[peer] = peerState
+ pref, err := netip.ParsePrefix(route)
+ if err != nil {
+ log.Errorf("failed to parse prefix %s: %v", route, err)
+ } else {
+ d.routeIDLookup.RemoveRemoteRouteID(pref)
+ }
+
// todo: consider to make sense of this notification or not
d.notifyPeerListChanged()
return nil
}
+// CheckRoutes checks if the source and destination addresses are within the same route
+// and returns the resource ID of the route that contains the addresses
+func (d *Status) CheckRoutes(ip netip.Addr) (resId string) {
+ if d == nil {
+ return
+ }
+ return d.routeIDLookup.Lookup(ip)
+}
+
func (d *Status) UpdatePeerICEState(receivedState State) error {
d.mux.Lock()
defer d.mux.Unlock()
@@ -558,6 +584,50 @@ func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
d.notifyAddressChanged()
}
+// AddLocalPeerStateRoute adds a route to the local peer state
+func (d *Status) AddLocalPeerStateRoute(route, resourceId string) {
+ d.mux.Lock()
+ defer d.mux.Unlock()
+
+ pref, err := netip.ParsePrefix(route)
+ if err != nil {
+ log.Errorf("failed to parse prefix %s: %v", route, err)
+ return
+ }
+
+ if d.localPeer.Routes == nil {
+ d.localPeer.Routes = map[string]struct{}{}
+ }
+
+ d.localPeer.Routes[route] = struct{}{}
+
+ d.routeIDLookup.AddLocalRouteID(resourceId, pref)
+}
+
+// RemoveLocalPeerStateRoute removes a route from the local peer state
+func (d *Status) RemoveLocalPeerStateRoute(route string) {
+ d.mux.Lock()
+ defer d.mux.Unlock()
+
+ pref, err := netip.ParsePrefix(route)
+ if err != nil {
+ log.Errorf("failed to parse prefix %s: %v", route, err)
+ return
+ }
+
+ delete(d.localPeer.Routes, route)
+
+ d.routeIDLookup.RemoveLocalRouteID(pref)
+}
+
+// CleanLocalPeerStateRoutes cleans all routes from the local peer state
+func (d *Status) CleanLocalPeerStateRoutes() {
+ d.mux.Lock()
+ defer d.mux.Unlock()
+
+ d.localPeer.Routes = map[string]struct{}{}
+}
+
// CleanLocalPeerState cleans local peer status
func (d *Status) CleanLocalPeerState() {
d.mux.Lock()
@@ -641,7 +711,7 @@ func (d *Status) UpdateDNSStates(dnsStates []NSGroupState) {
d.nsGroupStates = dnsStates
}
-func (d *Status) UpdateResolvedDomainsStates(originalDomain domain.Domain, resolvedDomain domain.Domain, prefixes []netip.Prefix) {
+func (d *Status) UpdateResolvedDomainsStates(originalDomain domain.Domain, resolvedDomain domain.Domain, prefixes []netip.Prefix, resourceId string) {
d.mux.Lock()
defer d.mux.Unlock()
@@ -650,6 +720,10 @@ func (d *Status) UpdateResolvedDomainsStates(originalDomain domain.Domain, resol
Prefixes: prefixes,
ParentDomain: originalDomain,
}
+
+ for _, prefix := range prefixes {
+ d.routeIDLookup.AddResolvedIP(resourceId, prefix)
+ }
}
func (d *Status) DeleteResolvedDomainsStates(domain domain.Domain) {
@@ -660,6 +734,10 @@ func (d *Status) DeleteResolvedDomainsStates(domain domain.Domain) {
for k, v := range d.resolvedDomainsStates {
if v.ParentDomain == domain {
delete(d.resolvedDomainsStates, k)
+
+ for _, prefix := range v.Prefixes {
+ d.routeIDLookup.RemoveResolvedIP(prefix)
+ }
}
}
}
diff --git a/client/internal/peer/worker_ice.go b/client/internal/peer/worker_ice.go
index 7dd84a98e..5ceb3f453 100644
--- a/client/internal/peer/worker_ice.go
+++ b/client/internal/peer/worker_ice.go
@@ -358,6 +358,12 @@ func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive
}
func candidateViaRoutes(candidate ice.Candidate, clientRoutes route.HAMap) bool {
+ addr, err := netip.ParseAddr(candidate.Address())
+ if err != nil {
+ log.Errorf("Failed to parse IP address %s: %v", candidate.Address(), err)
+ return false
+ }
+
var routePrefixes []netip.Prefix
for _, routes := range clientRoutes {
if len(routes) > 0 && routes[0] != nil {
@@ -365,14 +371,8 @@ func candidateViaRoutes(candidate ice.Candidate, clientRoutes route.HAMap) bool
}
}
- addr, err := netip.ParseAddr(candidate.Address())
- if err != nil {
- log.Errorf("Failed to parse IP address %s: %v", candidate.Address(), err)
- return false
- }
-
for _, prefix := range routePrefixes {
- // default route is
+ // default route is handled by route exclusion / ip rules
if prefix.Bits() == 0 {
continue
}
diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go
index 6680f727a..847949a53 100644
--- a/client/internal/routemanager/client.go
+++ b/client/internal/routemanager/client.go
@@ -330,7 +330,7 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem(rsn reason) error
c.connectEvent()
}
- err := c.statusRecorder.AddPeerStateRoute(c.currentChosen.Peer, c.handler.String())
+ err := c.statusRecorder.AddPeerStateRoute(c.currentChosen.Peer, c.handler.String(), c.currentChosen.GetResourceID())
if err != nil {
return fmt.Errorf("add peer state route: %w", err)
}
diff --git a/client/internal/routemanager/dnsinterceptor/handler.go b/client/internal/routemanager/dnsinterceptor/handler.go
index f36285cc4..2e6e4fede 100644
--- a/client/internal/routemanager/dnsinterceptor/handler.go
+++ b/client/internal/routemanager/dnsinterceptor/handler.go
@@ -160,6 +160,12 @@ func (d *DnsInterceptor) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
return
}
+ // set the AuthenticatedData flag and the EDNS0 buffer size to 4096 bytes to support larger dns records
+ if r.Extra == nil {
+ r.SetEdns0(4096, false)
+ r.MsgHdr.AuthenticatedData = true
+ }
+
client := &dns.Client{
Timeout: 5 * time.Second,
Net: "udp",
@@ -315,7 +321,7 @@ func (d *DnsInterceptor) updateDomainPrefixes(resolvedDomain, originalDomain dom
if len(toAdd) > 0 || len(toRemove) > 0 {
d.interceptedDomains[resolvedDomain] = newPrefixes
originalDomain = domain.Domain(strings.TrimSuffix(string(originalDomain), "."))
- d.statusRecorder.UpdateResolvedDomainsStates(originalDomain, resolvedDomain, newPrefixes)
+ d.statusRecorder.UpdateResolvedDomainsStates(originalDomain, resolvedDomain, newPrefixes, d.route.GetResourceID())
if len(toAdd) > 0 {
log.Debugf("added dynamic route(s) for domain=%s (pattern: domain=%s): %s",
diff --git a/client/internal/routemanager/dynamic/route.go b/client/internal/routemanager/dynamic/route.go
index 5ef18a47e..079134701 100644
--- a/client/internal/routemanager/dynamic/route.go
+++ b/client/internal/routemanager/dynamic/route.go
@@ -288,7 +288,7 @@ func (r *Route) updateDynamicRoutes(ctx context.Context, newDomains domainMap) e
updatedPrefixes := combinePrefixes(oldPrefixes, removedPrefixes, addedPrefixes)
r.dynamicDomains[domain] = updatedPrefixes
- r.statusRecorder.UpdateResolvedDomainsStates(domain, domain, updatedPrefixes)
+ r.statusRecorder.UpdateResolvedDomainsStates(domain, domain, updatedPrefixes, r.route.GetResourceID())
}
return nberrors.FormatErrorOrNil(merr)
diff --git a/client/internal/routemanager/iface/iface_common.go b/client/internal/routemanager/iface/iface_common.go
index 8b2dc9714..9e1f8058a 100644
--- a/client/internal/routemanager/iface/iface_common.go
+++ b/client/internal/routemanager/iface/iface_common.go
@@ -3,9 +3,9 @@ package iface
import (
"net"
- "github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/configurer"
"github.com/netbirdio/netbird/client/iface/device"
+ "github.com/netbirdio/netbird/client/iface/wgaddr"
)
type wgIfaceBase interface {
@@ -13,7 +13,7 @@ type wgIfaceBase interface {
RemoveAllowedIP(peerKey string, allowedIP string) error
Name() string
- Address() iface.WGAddress
+ Address() wgaddr.Address
ToInterface() *net.Interface
IsUserspaceBind() bool
GetFilter() device.PacketFilter
diff --git a/client/internal/routemanager/server_nonandroid.go b/client/internal/routemanager/server_nonandroid.go
index 5b6a788f8..ac2233d4e 100644
--- a/client/internal/routemanager/server_nonandroid.go
+++ b/client/internal/routemanager/server_nonandroid.go
@@ -103,9 +103,7 @@ func (m *serverRouter) removeFromServerNetwork(route *route.Route) error {
delete(m.routes, route.ID)
- state := m.statusRecorder.GetLocalPeerState()
- delete(state.Routes, route.Network.String())
- m.statusRecorder.UpdateLocalPeerState(state)
+ m.statusRecorder.RemoveLocalPeerStateRoute(route.Network.String())
return nil
}
@@ -131,18 +129,12 @@ func (m *serverRouter) addToServerNetwork(route *route.Route) error {
m.routes[route.ID] = route
- state := m.statusRecorder.GetLocalPeerState()
- if state.Routes == nil {
- state.Routes = map[string]struct{}{}
- }
-
routeStr := route.Network.String()
if route.IsDynamic() {
routeStr = route.Domains.SafeString()
}
- state.Routes[routeStr] = struct{}{}
- m.statusRecorder.UpdateLocalPeerState(state)
+ m.statusRecorder.AddLocalPeerStateRoute(routeStr, route.GetResourceID())
return nil
}
@@ -164,9 +156,7 @@ func (m *serverRouter) cleanUp() {
}
- state := m.statusRecorder.GetLocalPeerState()
- state.Routes = nil
- m.statusRecorder.UpdateLocalPeerState(state)
+ m.statusRecorder.CleanLocalPeerStateRoutes()
}
func routeToRouterPair(route *route.Route) (firewall.RouterPair, error) {
diff --git a/client/netbird.wxs b/client/netbird.wxs
index ee9ab667f..5e03a014d 100644
--- a/client/netbird.wxs
+++ b/client/netbird.wxs
@@ -71,7 +71,7 @@
-
+
diff --git a/client/resources.rc b/client/resources.rc
index ac411245e..696fd0dfa 100644
--- a/client/resources.rc
+++ b/client/resources.rc
@@ -5,5 +5,5 @@
#define STRINGIZE(x) #x
#define EXPAND(x) STRINGIZE(x)
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
-7 ICON ui/netbird.ico
+7 ICON ui/assets/netbird.ico
wintun.dll RCDATA wintun.dll
diff --git a/client/server/panic_windows.go b/client/server/panic_windows.go
index 1d4ba4b75..c5e73be7c 100644
--- a/client/server/panic_windows.go
+++ b/client/server/panic_windows.go
@@ -3,7 +3,7 @@ package server
import (
"fmt"
"os"
- "path/filepath"
+ "path"
"syscall"
log "github.com/sirupsen/logrus"
@@ -12,7 +12,6 @@ import (
)
const (
- windowsPanicLogEnvVar = "NB_WINDOWS_PANIC_LOG"
// STD_ERROR_HANDLE ((DWORD)-12) = 4294967284
stdErrorHandle = ^uintptr(11)
)
@@ -25,13 +24,10 @@ var (
)
func handlePanicLog() error {
- logPath := os.Getenv(windowsPanicLogEnvVar)
- if logPath == "" {
- return nil
- }
+ // TODO: move this to a central location
+ logDir := path.Join(os.Getenv("PROGRAMDATA"), "Netbird")
+ logPath := path.Join(logDir, "netbird.err")
- // Ensure the directory exists
- logDir := filepath.Dir(logPath)
if err := os.MkdirAll(logDir, 0750); err != nil {
return fmt.Errorf("create panic log directory: %w", err)
}
@@ -39,13 +35,11 @@ func handlePanicLog() error {
return fmt.Errorf("enforce permission on panic log file: %w", err)
}
- // Open log file with append mode
f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return fmt.Errorf("open panic log file: %w", err)
}
- // Redirect stderr to the file
if err = redirectStderr(f); err != nil {
if closeErr := f.Close(); closeErr != nil {
log.Warnf("failed to close file after redirect error: %v", closeErr)
@@ -59,7 +53,6 @@ func handlePanicLog() error {
// redirectStderr redirects stderr to the provided file
func redirectStderr(f *os.File) error {
- // Get the current process's stderr handle
if err := setStdHandle(f); err != nil {
return fmt.Errorf("failed to set stderr handle: %w", err)
}
diff --git a/client/server/server.go b/client/server/server.go
index 8907f541f..2d8f759cd 100644
--- a/client/server/server.go
+++ b/client/server/server.go
@@ -160,7 +160,7 @@ func (s *Server) Start() error {
// mechanism to keep the client connected even when the connection is lost.
// we cancel retry if the client receive a stop or down command, or if disable auto connect is configured.
func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Config, statusRecorder *peer.Status,
- runningChan chan error,
+ runningChan chan struct{},
) {
backOff := getConnectWithBackoff(ctx)
retryStarted := false
@@ -628,20 +628,21 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
- runningChan := make(chan error)
- go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, runningChan)
+ timeoutCtx, cancel := context.WithTimeout(callerCtx, 50*time.Second)
+ defer cancel()
+ runningChan := make(chan struct{}, 1) // buffered channel to do not lose the signal
+ go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, runningChan)
for {
select {
- case err := <-runningChan:
- if err != nil {
- log.Debugf("waiting for engine to become ready failed: %s", err)
- } else {
- return &proto.UpResponse{}, nil
- }
+ case <-runningChan:
+ return &proto.UpResponse{}, nil
case <-callerCtx.Done():
log.Debug("context done, stopping the wait for engine to become ready")
return nil, callerCtx.Err()
+ case <-timeoutCtx.Done():
+ log.Debug("up is timed out, stopping the wait for engine to become ready")
+ return nil, timeoutCtx.Err()
}
}
}
diff --git a/client/server/trace.go b/client/server/trace.go
index 66b83d8cf..8b9d375f3 100644
--- a/client/server/trace.go
+++ b/client/server/trace.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net"
+ "net/netip"
fw "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/firewall/uspfilter"
@@ -41,11 +42,21 @@ func (s *Server) TracePacket(_ context.Context, req *proto.TracePacketRequest) (
srcIP = engine.GetWgAddr()
}
+ srcAddr, ok := netip.AddrFromSlice(srcIP)
+ if !ok {
+ return nil, fmt.Errorf("invalid source IP address")
+ }
+
dstIP := net.ParseIP(req.GetDestinationIp())
if req.GetDestinationIp() == "self" {
dstIP = engine.GetWgAddr()
}
+ dstAddr, ok := netip.AddrFromSlice(dstIP)
+ if !ok {
+ return nil, fmt.Errorf("invalid source IP address")
+ }
+
if srcIP == nil || dstIP == nil {
return nil, fmt.Errorf("invalid IP address")
}
@@ -85,8 +96,8 @@ func (s *Server) TracePacket(_ context.Context, req *proto.TracePacketRequest) (
}
builder := &uspfilter.PacketBuilder{
- SrcIP: srcIP,
- DstIP: dstIP,
+ SrcIP: srcAddr,
+ DstIP: dstAddr,
Protocol: protocol,
SrcPort: uint16(req.GetSourcePort()),
DstPort: uint16(req.GetDestinationPort()),
diff --git a/client/ui/netbird-systemtray-connected-dark.ico b/client/ui/assets/netbird-systemtray-connected-dark.ico
similarity index 100%
rename from client/ui/netbird-systemtray-connected-dark.ico
rename to client/ui/assets/netbird-systemtray-connected-dark.ico
diff --git a/client/ui/netbird-systemtray-connected-dark.png b/client/ui/assets/netbird-systemtray-connected-dark.png
similarity index 100%
rename from client/ui/netbird-systemtray-connected-dark.png
rename to client/ui/assets/netbird-systemtray-connected-dark.png
diff --git a/client/ui/netbird-systemtray-connected-macos.png b/client/ui/assets/netbird-systemtray-connected-macos.png
similarity index 100%
rename from client/ui/netbird-systemtray-connected-macos.png
rename to client/ui/assets/netbird-systemtray-connected-macos.png
diff --git a/client/ui/netbird-systemtray-connected.ico b/client/ui/assets/netbird-systemtray-connected.ico
similarity index 100%
rename from client/ui/netbird-systemtray-connected.ico
rename to client/ui/assets/netbird-systemtray-connected.ico
diff --git a/client/ui/netbird-systemtray-connected.png b/client/ui/assets/netbird-systemtray-connected.png
similarity index 100%
rename from client/ui/netbird-systemtray-connected.png
rename to client/ui/assets/netbird-systemtray-connected.png
diff --git a/client/ui/netbird-systemtray-connecting-dark.ico b/client/ui/assets/netbird-systemtray-connecting-dark.ico
similarity index 100%
rename from client/ui/netbird-systemtray-connecting-dark.ico
rename to client/ui/assets/netbird-systemtray-connecting-dark.ico
diff --git a/client/ui/netbird-systemtray-connecting-dark.png b/client/ui/assets/netbird-systemtray-connecting-dark.png
similarity index 100%
rename from client/ui/netbird-systemtray-connecting-dark.png
rename to client/ui/assets/netbird-systemtray-connecting-dark.png
diff --git a/client/ui/netbird-systemtray-connecting-macos.png b/client/ui/assets/netbird-systemtray-connecting-macos.png
similarity index 100%
rename from client/ui/netbird-systemtray-connecting-macos.png
rename to client/ui/assets/netbird-systemtray-connecting-macos.png
diff --git a/client/ui/netbird-systemtray-connecting.ico b/client/ui/assets/netbird-systemtray-connecting.ico
similarity index 100%
rename from client/ui/netbird-systemtray-connecting.ico
rename to client/ui/assets/netbird-systemtray-connecting.ico
diff --git a/client/ui/netbird-systemtray-connecting.png b/client/ui/assets/netbird-systemtray-connecting.png
similarity index 100%
rename from client/ui/netbird-systemtray-connecting.png
rename to client/ui/assets/netbird-systemtray-connecting.png
diff --git a/client/ui/netbird-systemtray-disconnected-macos.png b/client/ui/assets/netbird-systemtray-disconnected-macos.png
similarity index 100%
rename from client/ui/netbird-systemtray-disconnected-macos.png
rename to client/ui/assets/netbird-systemtray-disconnected-macos.png
diff --git a/client/ui/netbird-systemtray-disconnected.ico b/client/ui/assets/netbird-systemtray-disconnected.ico
similarity index 100%
rename from client/ui/netbird-systemtray-disconnected.ico
rename to client/ui/assets/netbird-systemtray-disconnected.ico
diff --git a/client/ui/netbird-systemtray-disconnected.png b/client/ui/assets/netbird-systemtray-disconnected.png
similarity index 100%
rename from client/ui/netbird-systemtray-disconnected.png
rename to client/ui/assets/netbird-systemtray-disconnected.png
diff --git a/client/ui/netbird-systemtray-error-dark.ico b/client/ui/assets/netbird-systemtray-error-dark.ico
similarity index 100%
rename from client/ui/netbird-systemtray-error-dark.ico
rename to client/ui/assets/netbird-systemtray-error-dark.ico
diff --git a/client/ui/netbird-systemtray-error-dark.png b/client/ui/assets/netbird-systemtray-error-dark.png
similarity index 100%
rename from client/ui/netbird-systemtray-error-dark.png
rename to client/ui/assets/netbird-systemtray-error-dark.png
diff --git a/client/ui/netbird-systemtray-error-macos.png b/client/ui/assets/netbird-systemtray-error-macos.png
similarity index 100%
rename from client/ui/netbird-systemtray-error-macos.png
rename to client/ui/assets/netbird-systemtray-error-macos.png
diff --git a/client/ui/netbird-systemtray-error.ico b/client/ui/assets/netbird-systemtray-error.ico
similarity index 100%
rename from client/ui/netbird-systemtray-error.ico
rename to client/ui/assets/netbird-systemtray-error.ico
diff --git a/client/ui/netbird-systemtray-error.png b/client/ui/assets/netbird-systemtray-error.png
similarity index 100%
rename from client/ui/netbird-systemtray-error.png
rename to client/ui/assets/netbird-systemtray-error.png
diff --git a/client/ui/netbird-systemtray-update-connected-dark.ico b/client/ui/assets/netbird-systemtray-update-connected-dark.ico
similarity index 100%
rename from client/ui/netbird-systemtray-update-connected-dark.ico
rename to client/ui/assets/netbird-systemtray-update-connected-dark.ico
diff --git a/client/ui/netbird-systemtray-update-connected-dark.png b/client/ui/assets/netbird-systemtray-update-connected-dark.png
similarity index 100%
rename from client/ui/netbird-systemtray-update-connected-dark.png
rename to client/ui/assets/netbird-systemtray-update-connected-dark.png
diff --git a/client/ui/netbird-systemtray-update-connected-macos.png b/client/ui/assets/netbird-systemtray-update-connected-macos.png
similarity index 100%
rename from client/ui/netbird-systemtray-update-connected-macos.png
rename to client/ui/assets/netbird-systemtray-update-connected-macos.png
diff --git a/client/ui/netbird-systemtray-update-connected.ico b/client/ui/assets/netbird-systemtray-update-connected.ico
similarity index 100%
rename from client/ui/netbird-systemtray-update-connected.ico
rename to client/ui/assets/netbird-systemtray-update-connected.ico
diff --git a/client/ui/netbird-systemtray-update-connected.png b/client/ui/assets/netbird-systemtray-update-connected.png
similarity index 100%
rename from client/ui/netbird-systemtray-update-connected.png
rename to client/ui/assets/netbird-systemtray-update-connected.png
diff --git a/client/ui/netbird-systemtray-update-disconnected-dark.ico b/client/ui/assets/netbird-systemtray-update-disconnected-dark.ico
similarity index 100%
rename from client/ui/netbird-systemtray-update-disconnected-dark.ico
rename to client/ui/assets/netbird-systemtray-update-disconnected-dark.ico
diff --git a/client/ui/netbird-systemtray-update-disconnected-dark.png b/client/ui/assets/netbird-systemtray-update-disconnected-dark.png
similarity index 100%
rename from client/ui/netbird-systemtray-update-disconnected-dark.png
rename to client/ui/assets/netbird-systemtray-update-disconnected-dark.png
diff --git a/client/ui/netbird-systemtray-update-disconnected-macos.png b/client/ui/assets/netbird-systemtray-update-disconnected-macos.png
similarity index 100%
rename from client/ui/netbird-systemtray-update-disconnected-macos.png
rename to client/ui/assets/netbird-systemtray-update-disconnected-macos.png
diff --git a/client/ui/netbird-systemtray-update-disconnected.ico b/client/ui/assets/netbird-systemtray-update-disconnected.ico
similarity index 100%
rename from client/ui/netbird-systemtray-update-disconnected.ico
rename to client/ui/assets/netbird-systemtray-update-disconnected.ico
diff --git a/client/ui/netbird-systemtray-update-disconnected.png b/client/ui/assets/netbird-systemtray-update-disconnected.png
similarity index 100%
rename from client/ui/netbird-systemtray-update-disconnected.png
rename to client/ui/assets/netbird-systemtray-update-disconnected.png
diff --git a/client/ui/netbird.ico b/client/ui/assets/netbird.ico
similarity index 100%
rename from client/ui/netbird.ico
rename to client/ui/assets/netbird.ico
diff --git a/client/ui/netbird.png b/client/ui/assets/netbird.png
similarity index 100%
rename from client/ui/netbird.png
rename to client/ui/assets/netbird.png
diff --git a/client/ui/banner.bmp b/client/ui/build/banner.bmp
similarity index 100%
rename from client/ui/banner.bmp
rename to client/ui/build/banner.bmp
diff --git a/client/ui/build-ui-linux.sh b/client/ui/build/build-ui-linux.sh
similarity index 100%
rename from client/ui/build-ui-linux.sh
rename to client/ui/build/build-ui-linux.sh
diff --git a/client/ui/netbird.desktop b/client/ui/build/netbird.desktop
similarity index 100%
rename from client/ui/netbird.desktop
rename to client/ui/build/netbird.desktop
diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go
index 51eec59a5..914d4f3b9 100644
--- a/client/ui/client_ui.go
+++ b/client/ui/client_ui.go
@@ -35,7 +35,9 @@ import (
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/ui/desktop"
"github.com/netbirdio/netbird/client/ui/event"
+ "github.com/netbirdio/netbird/client/ui/process"
"github.com/netbirdio/netbird/util"
+
"github.com/netbirdio/netbird/version"
)
@@ -44,94 +46,125 @@ const (
failFastTimeout = time.Second
)
+const (
+ censoredPreSharedKey = "**********"
+)
+
func main() {
- var daemonAddr string
-
- defaultDaemonAddr := "unix:///var/run/netbird.sock"
- if runtime.GOOS == "windows" {
- defaultDaemonAddr = "tcp://127.0.0.1:41731"
- }
-
- flag.StringVar(
- &daemonAddr, "daemon-addr",
- defaultDaemonAddr,
- "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
-
- var showSettings bool
- flag.BoolVar(&showSettings, "settings", false, "run settings windows")
- var showRoutes bool
- flag.BoolVar(&showRoutes, "networks", false, "run networks windows")
- var errorMSG string
- flag.StringVar(&errorMSG, "error-msg", "", "displays a error message window")
-
- tmpDir := "/tmp"
- if runtime.GOOS == "windows" {
- tmpDir = os.TempDir()
- }
-
- var saveLogsInFile bool
- flag.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", tmpDir))
-
- flag.Parse()
+ daemonAddr, showSettings, showNetworks, errorMsg, saveLogsInFile := parseFlags()
+ // Initialize file logging if needed.
if saveLogsInFile {
- logFile := path.Join(tmpDir, fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
- err := util.InitLog("trace", logFile)
- if err != nil {
+ if err := initLogFile(); err != nil {
log.Errorf("error while initializing log: %v", err)
return
}
}
+ // Create the Fyne application.
a := app.NewWithID("NetBird")
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnected))
- if errorMSG != "" {
- showErrorMSG(errorMSG)
+ // Show error message window if needed.
+ if errorMsg != "" {
+ showErrorMessage(errorMsg)
return
}
- client := newServiceClient(daemonAddr, a, showSettings, showRoutes)
+ // Create the service client (this also builds the settings or networks UI if requested).
+ client := newServiceClient(daemonAddr, a, showSettings, showNetworks)
+
+ // Watch for theme/settings changes to update the icon.
+ go watchSettingsChanges(a, client)
+
+ // Run in window mode if any UI flag was set.
+ if showSettings || showNetworks {
+ a.Run()
+ return
+ }
+
+ // Check for another running process.
+ running, err := process.IsAnotherProcessRunning()
+ if err != nil {
+ log.Errorf("error while checking process: %v", err)
+ return
+ }
+ if running {
+ log.Warn("another process is running")
+ return
+ }
+
+ client.setDefaultFonts()
+ systray.Run(client.onTrayReady, client.onTrayExit)
+}
+
+// parseFlags reads and returns all needed command-line flags.
+func parseFlags() (daemonAddr string, showSettings, showNetworks bool, errorMsg string, saveLogsInFile bool) {
+ defaultDaemonAddr := "unix:///var/run/netbird.sock"
+ if runtime.GOOS == "windows" {
+ defaultDaemonAddr = "tcp://127.0.0.1:41731"
+ }
+ flag.StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
+ flag.BoolVar(&showSettings, "settings", false, "run settings window")
+ flag.BoolVar(&showNetworks, "networks", false, "run networks window")
+ flag.StringVar(&errorMsg, "error-msg", "", "displays an error message window")
+
+ tmpDir := "/tmp"
+ if runtime.GOOS == "windows" {
+ tmpDir = os.TempDir()
+ }
+ flag.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", tmpDir))
+ flag.Parse()
+ return
+}
+
+// initLogFile initializes logging into a file.
+func initLogFile() error {
+ tmpDir := "/tmp"
+ if runtime.GOOS == "windows" {
+ tmpDir = os.TempDir()
+ }
+ logFile := path.Join(tmpDir, fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
+ return util.InitLog("trace", logFile)
+}
+
+// watchSettingsChanges listens for Fyne theme/settings changes and updates the client icon.
+func watchSettingsChanges(a fyne.App, client *serviceClient) {
settingsChangeChan := make(chan fyne.Settings)
a.Settings().AddChangeListener(settingsChangeChan)
- go func() {
- for range settingsChangeChan {
- client.updateIcon()
- }
- }()
-
- if showSettings || showRoutes {
- a.Run()
- } else {
- running, err := isAnotherProcessRunning()
- if err != nil {
- log.Errorf("error while checking process: %v", err)
- }
- if running {
- log.Warn("another process is running")
- return
- }
- client.setDefaultFonts()
- systray.Run(client.onTrayReady, client.onTrayExit)
+ for range settingsChangeChan {
+ client.updateIcon()
}
}
-//go:embed netbird-systemtray-connected-macos.png
+// showErrorMessage displays an error message in a simple window.
+func showErrorMessage(msg string) {
+ a := app.New()
+ w := a.NewWindow("NetBird Error")
+ label := widget.NewLabel(msg)
+ label.Wrapping = fyne.TextWrapWord
+ w.SetContent(label)
+ w.Resize(fyne.NewSize(400, 100))
+ w.Show()
+ a.Run()
+}
+
+//go:embed assets/netbird-systemtray-connected-macos.png
var iconConnectedMacOS []byte
-//go:embed netbird-systemtray-disconnected-macos.png
+//go:embed assets/netbird-systemtray-disconnected-macos.png
var iconDisconnectedMacOS []byte
-//go:embed netbird-systemtray-update-disconnected-macos.png
+//go:embed assets/netbird-systemtray-update-disconnected-macos.png
var iconUpdateDisconnectedMacOS []byte
-//go:embed netbird-systemtray-update-connected-macos.png
+//go:embed assets/netbird-systemtray-update-connected-macos.png
var iconUpdateConnectedMacOS []byte
-//go:embed netbird-systemtray-connecting-macos.png
+//go:embed assets/netbird-systemtray-connecting-macos.png
var iconConnectingMacOS []byte
-//go:embed netbird-systemtray-error-macos.png
+//go:embed assets/netbird-systemtray-error-macos.png
var iconErrorMacOS []byte
type serviceClient struct {
@@ -154,6 +187,7 @@ type serviceClient struct {
mAdminPanel *systray.MenuItem
mSettings *systray.MenuItem
mAbout *systray.MenuItem
+ mGitHub *systray.MenuItem
mVersionUI *systray.MenuItem
mVersionDaemon *systray.MenuItem
mUpdate *systray.MenuItem
@@ -300,18 +334,6 @@ func (s *serviceClient) showSettingsUI() {
s.wSettings.Show()
}
-// showErrorMSG opens a fyne app window to display the supplied message
-func showErrorMSG(msg string) {
- app := app.New()
- w := app.NewWindow("NetBird Error")
- content := widget.NewLabel(msg)
- content.Wrapping = fyne.TextWrapWord
- w.SetContent(content)
- w.Resize(fyne.NewSize(400, 100))
- w.Show()
- app.Run()
-}
-
// getSettingsForm to embed it into settings window.
func (s *serviceClient) getSettingsForm() *widget.Form {
return &widget.Form{
@@ -327,7 +349,7 @@ func (s *serviceClient) getSettingsForm() *widget.Form {
},
SubmitText: "Save",
OnSubmit: func() {
- if s.iPreSharedKey.Text != "" && s.iPreSharedKey.Text != "**********" {
+ if s.iPreSharedKey.Text != "" && s.iPreSharedKey.Text != censoredPreSharedKey {
// validate preSharedKey if it added
if _, err := wgtypes.ParseKey(s.iPreSharedKey.Text); err != nil {
dialog.ShowError(fmt.Errorf("Invalid Pre-shared Key Value"), s.wSettings)
@@ -365,7 +387,7 @@ func (s *serviceClient) getSettingsForm() *widget.Form {
WireguardPort: &port,
}
- if s.iPreSharedKey.Text != "**********" {
+ if s.iPreSharedKey.Text != censoredPreSharedKey {
loginRequest.OptionalPreSharedKey = &s.iPreSharedKey.Text
}
@@ -587,26 +609,29 @@ func (s *serviceClient) onTrayReady() {
s.mAdminPanel = systray.AddMenuItem("Admin Panel", "Netbird Admin Panel")
systray.AddSeparator()
- s.mSettings = systray.AddMenuItem("Settings", "Settings of the application")
- 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.mEnableRosenpass = s.mSettings.AddSubMenuItemCheckbox("Enable Quantum-Resistance", "Enable post-quantum security via Rosenpass", false)
- s.mNotifications = s.mSettings.AddSubMenuItemCheckbox("Notifications", "Enable notifications", false)
- s.mAdvancedSettings = s.mSettings.AddSubMenuItem("Advanced Settings", "Advanced settings of the application")
- s.mCreateDebugBundle = s.mSettings.AddSubMenuItem("Create Debug Bundle", "Create and open debug information bundle")
+ s.mSettings = systray.AddMenuItem("Settings", settingsMenuDescr)
+ s.mAllowSSH = s.mSettings.AddSubMenuItemCheckbox("Allow SSH", allowSSHMenuDescr, false)
+ s.mAutoConnect = s.mSettings.AddSubMenuItemCheckbox("Connect on Startup", autoConnectMenuDescr, false)
+ s.mEnableRosenpass = s.mSettings.AddSubMenuItemCheckbox("Enable Quantum-Resistance", quantumResistanceMenuDescr, false)
+ s.mNotifications = s.mSettings.AddSubMenuItemCheckbox("Notifications", notificationsMenuDescr, false)
+ s.mAdvancedSettings = s.mSettings.AddSubMenuItem("Advanced Settings", advancedSettingsMenuDescr)
+ s.mCreateDebugBundle = s.mSettings.AddSubMenuItem("Create Debug Bundle", debugBundleMenuDescr)
s.loadSettings()
s.exitNodeMu.Lock()
- s.mExitNode = systray.AddMenuItem("Exit Node", "Select exit node for routing traffic")
+ s.mExitNode = systray.AddMenuItem("Exit Node", exitNodeMenuDescr)
s.mExitNode.Disable()
s.exitNodeMu.Unlock()
- s.mNetworks = systray.AddMenuItem("Networks", "Open the networks management window")
+ s.mNetworks = systray.AddMenuItem("Networks", networksMenuDescr)
s.mNetworks.Disable()
systray.AddSeparator()
s.mAbout = systray.AddMenuItem("About", "About")
s.mAbout.SetIcon(s.icAbout)
+
+ s.mGitHub = s.mAbout.AddSubMenuItem("GitHub", "GitHub")
+
versionString := normalizedVersion(version.NetbirdVersion())
s.mVersionUI = s.mAbout.AddSubMenuItem(fmt.Sprintf("GUI: %s", versionString), fmt.Sprintf("GUI Version: %s", versionString))
s.mVersionUI.Disable()
@@ -615,11 +640,11 @@ func (s *serviceClient) onTrayReady() {
s.mVersionDaemon.Disable()
s.mVersionDaemon.Hide()
- s.mUpdate = s.mAbout.AddSubMenuItem("Download latest version", "Download latest version")
+ s.mUpdate = s.mAbout.AddSubMenuItem("Download latest version", latestVersionMenuDescr)
s.mUpdate.Hide()
systray.AddSeparator()
- s.mQuit = systray.AddMenuItem("Quit", "Quit the client app")
+ s.mQuit = systray.AddMenuItem("Quit", quitMenuDescr)
// update exit node menu in case service is already connected
go s.updateExitNodes()
@@ -717,6 +742,11 @@ func (s *serviceClient) onTrayReady() {
case <-s.mQuit.ClickedCh:
systray.Quit()
return
+ case <-s.mGitHub.ClickedCh:
+ err := openURL("https://github.com/netbirdio/netbird")
+ if err != nil {
+ log.Errorf("%s", err)
+ }
case <-s.mUpdate.ClickedCh:
err := openURL(version.DownloadUrl())
if err != nil {
diff --git a/client/ui/const.go b/client/ui/const.go
new file mode 100644
index 000000000..0253750d1
--- /dev/null
+++ b/client/ui/const.go
@@ -0,0 +1,15 @@
+package main
+
+const (
+ settingsMenuDescr = "Settings of the application"
+ allowSSHMenuDescr = "Allow SSH connections"
+ autoConnectMenuDescr = "Connect automatically when the service starts"
+ quantumResistanceMenuDescr = "Enable post-quantum security via Rosenpass"
+ notificationsMenuDescr = "Enable notifications"
+ advancedSettingsMenuDescr = "Advanced settings of the application"
+ debugBundleMenuDescr = "Create and open debug information bundle"
+ exitNodeMenuDescr = "Select exit node for routing traffic"
+ networksMenuDescr = "Open the networks management window"
+ latestVersionMenuDescr = "Download latest version"
+ quitMenuDescr = "Quit the client app"
+)
diff --git a/client/ui/icons.go b/client/ui/icons.go
index 6f3a9dbc9..e88fb9378 100644
--- a/client/ui/icons.go
+++ b/client/ui/icons.go
@@ -6,38 +6,38 @@ import (
_ "embed"
)
-//go:embed netbird.png
+//go:embed assets/netbird.png
var iconAbout []byte
-//go:embed netbird-systemtray-connected.png
+//go:embed assets/netbird-systemtray-connected.png
var iconConnected []byte
-//go:embed netbird-systemtray-connected-dark.png
+//go:embed assets/netbird-systemtray-connected-dark.png
var iconConnectedDark []byte
-//go:embed netbird-systemtray-disconnected.png
+//go:embed assets/netbird-systemtray-disconnected.png
var iconDisconnected []byte
-//go:embed netbird-systemtray-update-disconnected.png
+//go:embed assets/netbird-systemtray-update-disconnected.png
var iconUpdateDisconnected []byte
-//go:embed netbird-systemtray-update-disconnected-dark.png
+//go:embed assets/netbird-systemtray-update-disconnected-dark.png
var iconUpdateDisconnectedDark []byte
-//go:embed netbird-systemtray-update-connected.png
+//go:embed assets/netbird-systemtray-update-connected.png
var iconUpdateConnected []byte
-//go:embed netbird-systemtray-update-connected-dark.png
+//go:embed assets/netbird-systemtray-update-connected-dark.png
var iconUpdateConnectedDark []byte
-//go:embed netbird-systemtray-connecting.png
+//go:embed assets/netbird-systemtray-connecting.png
var iconConnecting []byte
-//go:embed netbird-systemtray-connecting-dark.png
+//go:embed assets/netbird-systemtray-connecting-dark.png
var iconConnectingDark []byte
-//go:embed netbird-systemtray-error.png
+//go:embed assets/netbird-systemtray-error.png
var iconError []byte
-//go:embed netbird-systemtray-error-dark.png
+//go:embed assets/netbird-systemtray-error-dark.png
var iconErrorDark []byte
diff --git a/client/ui/icons_windows.go b/client/ui/icons_windows.go
index a2a924763..2107d3852 100644
--- a/client/ui/icons_windows.go
+++ b/client/ui/icons_windows.go
@@ -1,41 +1,41 @@
package main
import (
- _ "embed"
+ _ "embed"
)
-//go:embed netbird.ico
+//go:embed assets/netbird.ico
var iconAbout []byte
-//go:embed netbird-systemtray-connected.ico
+//go:embed assets/netbird-systemtray-connected.ico
var iconConnected []byte
-//go:embed netbird-systemtray-connected-dark.ico
+//go:embed assets/netbird-systemtray-connected-dark.ico
var iconConnectedDark []byte
-//go:embed netbird-systemtray-disconnected.ico
+//go:embed assets/netbird-systemtray-disconnected.ico
var iconDisconnected []byte
-//go:embed netbird-systemtray-update-disconnected.ico
+//go:embed assets/netbird-systemtray-update-disconnected.ico
var iconUpdateDisconnected []byte
-//go:embed netbird-systemtray-update-disconnected-dark.ico
+//go:embed assets/netbird-systemtray-update-disconnected-dark.ico
var iconUpdateDisconnectedDark []byte
-//go:embed netbird-systemtray-update-connected.ico
+//go:embed assets/netbird-systemtray-update-connected.ico
var iconUpdateConnected []byte
-//go:embed netbird-systemtray-update-connected-dark.ico
+//go:embed assets/netbird-systemtray-update-connected-dark.ico
var iconUpdateConnectedDark []byte
-//go:embed netbird-systemtray-connecting.ico
+//go:embed assets/netbird-systemtray-connecting.ico
var iconConnecting []byte
-//go:embed netbird-systemtray-connecting-dark.ico
+//go:embed assets/netbird-systemtray-connecting-dark.ico
var iconConnectingDark []byte
-//go:embed netbird-systemtray-error.ico
+//go:embed assets/netbird-systemtray-error.ico
var iconError []byte
-//go:embed netbird-systemtray-error-dark.ico
+//go:embed assets/netbird-systemtray-error-dark.ico
var iconErrorDark []byte
diff --git a/client/ui/network.go b/client/ui/network.go
index 750788cf3..b21554f09 100644
--- a/client/ui/network.go
+++ b/client/ui/network.go
@@ -363,7 +363,7 @@ func (s *serviceClient) recreateExitNodeMenu(exitNodes []*proto.Network) {
if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" {
s.mExitNode.Remove()
- s.mExitNode = systray.AddMenuItem("Exit Node", "Select exit node for routing traffic")
+ s.mExitNode = systray.AddMenuItem("Exit Node", exitNodeMenuDescr)
}
for _, node := range exitNodes {
diff --git a/client/ui/process.go b/client/ui/process/process.go
similarity index 90%
rename from client/ui/process.go
rename to client/ui/process/process.go
index bcb3dd879..f9a8a4fe9 100644
--- a/client/ui/process.go
+++ b/client/ui/process/process.go
@@ -1,4 +1,4 @@
-package main
+package process
import (
"os"
@@ -8,7 +8,7 @@ import (
"github.com/shirou/gopsutil/v3/process"
)
-func isAnotherProcessRunning() (bool, error) {
+func IsAnotherProcessRunning() (bool, error) {
processes, err := process.Processes()
if err != nil {
return false, err
diff --git a/client/ui/process_nonwindows.go b/client/ui/process/process_nonwindows.go
similarity index 96%
rename from client/ui/process_nonwindows.go
rename to client/ui/process/process_nonwindows.go
index 0d17be2be..cf9f6443d 100644
--- a/client/ui/process_nonwindows.go
+++ b/client/ui/process/process_nonwindows.go
@@ -1,6 +1,6 @@
//go:build !windows
-package main
+package process
import (
"os"
diff --git a/client/ui/process_windows.go b/client/ui/process/process_windows.go
similarity index 96%
rename from client/ui/process_windows.go
rename to client/ui/process/process_windows.go
index b15b0ed24..2d211d1a4 100644
--- a/client/ui/process_windows.go
+++ b/client/ui/process/process_windows.go
@@ -1,4 +1,4 @@
-package main
+package process
import (
"os/user"
diff --git a/flow/client/client.go b/flow/client/client.go
index 47c80ef0d..b16b28c64 100644
--- a/flow/client/client.go
+++ b/flow/client/client.go
@@ -4,8 +4,10 @@ import (
"context"
"crypto/tls"
"crypto/x509"
+ "errors"
"fmt"
"strings"
+ "sync"
"time"
"github.com/cenkalti/backoff/v4"
@@ -25,95 +27,99 @@ type GRPCClient struct {
realClient proto.FlowServiceClient
clientConn *grpc.ClientConn
stream proto.FlowService_EventsClient
+ streamMu sync.Mutex
}
-func NewClient(ctx context.Context, addr, payload, signature string) (*GRPCClient, error) {
+func NewClient(addr, payload, signature string, interval time.Duration) (*GRPCClient, error) {
+ var opts []grpc.DialOption
- transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
if strings.Contains(addr, "443") {
-
certPool, err := x509.SystemCertPool()
if err != nil || certPool == nil {
log.Debugf("System cert pool not available; falling back to embedded cert, error: %v", err)
certPool = embeddedroots.Get()
}
- transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
+ opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
RootCAs: certPool,
- }))
+ })))
+ } else {
+ opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
- connCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
-
- conn, err := grpc.DialContext(
- connCtx,
- addr,
- transportOption,
+ opts = append(opts,
nbgrpc.WithCustomDialer(),
- grpc.WithBlock(),
+ grpc.WithIdleTimeout(interval*2),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
Timeout: 10 * time.Second,
}),
withAuthToken(payload, signature),
+ grpc.WithDefaultServiceConfig(`{"healthCheckConfig": {"serviceName": ""}}`),
)
+ conn, err := grpc.NewClient(addr, opts...)
if err != nil {
- return nil, fmt.Errorf("dialing with context: %s", err)
+ return nil, fmt.Errorf("creating new grpc client: %w", err)
}
- client := &GRPCClient{
+ return &GRPCClient{
realClient: proto.NewFlowServiceClient(conn),
clientConn: conn,
- }
- return client, nil
+ }, nil
}
func (c *GRPCClient) Close() error {
+ c.streamMu.Lock()
+ defer c.streamMu.Unlock()
+
+ c.stream = nil
return c.clientConn.Close()
}
-func (c *GRPCClient) Receive(ctx context.Context, msgHandler func(msg *proto.FlowEventAck) error) error {
- backOff := defaultBackoff(ctx)
+func (c *GRPCClient) Receive(ctx context.Context, interval time.Duration, msgHandler func(msg *proto.FlowEventAck) error) error {
+ backOff := defaultBackoff(ctx, interval)
operation := func() error {
- connState := c.clientConn.GetState()
- if connState == connectivity.Shutdown {
- return backoff.Permanent(fmt.Errorf("connection to signal has been shut down"))
- }
-
- stream, err := c.realClient.Events(ctx, grpc.WaitForReady(true))
- if err != nil {
- return err
- }
- c.stream = stream
-
- err = checkHeader(stream)
- if err != nil {
- return err
- }
-
- return c.receive(stream, msgHandler)
+ return c.establishStreamAndReceive(ctx, msgHandler)
}
- err := backoff.Retry(operation, backOff)
- if err != nil {
- log.Errorf("exiting the flow receiver service connection retry loop due to the unrecoverable error: %v", err)
- return err
+ if err := backoff.Retry(operation, backOff); err != nil {
+ return fmt.Errorf("receive failed permanently: %w", err)
}
return nil
}
+func (c *GRPCClient) establishStreamAndReceive(ctx context.Context, msgHandler func(msg *proto.FlowEventAck) error) error {
+ if c.clientConn.GetState() == connectivity.Shutdown {
+ return backoff.Permanent(errors.New("connection to flow receiver has been shut down"))
+ }
+
+ stream, err := c.realClient.Events(ctx, grpc.WaitForReady(true))
+ if err != nil {
+ return fmt.Errorf("create event stream: %w", err)
+ }
+
+ if err = checkHeader(stream); err != nil {
+ return fmt.Errorf("check header: %w", err)
+ }
+
+ c.streamMu.Lock()
+ c.stream = stream
+ c.streamMu.Unlock()
+
+ return c.receive(stream, msgHandler)
+}
+
func (c *GRPCClient) receive(stream proto.FlowService_EventsClient, msgHandler func(msg *proto.FlowEventAck) error) error {
for {
msg, err := stream.Recv()
if err != nil {
- return err
+ return fmt.Errorf("receive from stream: %w", err)
}
if err := msgHandler(msg); err != nil {
- return err
+ return fmt.Errorf("handle message: %w", err)
}
}
}
@@ -122,7 +128,7 @@ func checkHeader(stream proto.FlowService_EventsClient) error {
header, err := stream.Header()
if err != nil {
log.Errorf("waiting for flow receiver header: %s", err)
- return err
+ return fmt.Errorf("wait for header: %w", err)
}
if len(header) == 0 {
@@ -132,26 +138,29 @@ func checkHeader(stream proto.FlowService_EventsClient) error {
return nil
}
-func defaultBackoff(ctx context.Context) backoff.BackOff {
+func defaultBackoff(ctx context.Context, interval time.Duration) backoff.BackOff {
return backoff.WithContext(&backoff.ExponentialBackOff{
InitialInterval: 800 * time.Millisecond,
RandomizationFactor: 1,
Multiplier: 1.7,
- MaxInterval: 10 * time.Second,
+ MaxInterval: interval / 2,
MaxElapsedTime: 3 * 30 * 24 * time.Hour, // 3 months
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}, ctx)
}
-func (c *GRPCClient) Send(ctx context.Context, event *proto.FlowEvent) error {
- if c.stream == nil {
- return fmt.Errorf("stream not initialized")
+func (c *GRPCClient) Send(event *proto.FlowEvent) error {
+ c.streamMu.Lock()
+ stream := c.stream
+ c.streamMu.Unlock()
+
+ if stream == nil {
+ return errors.New("stream not initialized")
}
- err := c.stream.Send(event)
- if err != nil {
- return fmt.Errorf("sending flow event: %s", err)
+ if err := stream.Send(event); err != nil {
+ return fmt.Errorf("send flow event: %w", err)
}
return nil
diff --git a/flow/proto/flow.pb.go b/flow/proto/flow.pb.go
index b2857c2b2..8b34b0f62 100644
--- a/flow/proto/flow.pb.go
+++ b/flow/proto/flow.pb.go
@@ -130,7 +130,7 @@ type FlowEvent struct {
unknownFields protoimpl.UnknownFields
// Unique client event identifier
- EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
+ EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
// When the event occurred
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// Public key of the sending peer
@@ -170,11 +170,11 @@ func (*FlowEvent) Descriptor() ([]byte, []int) {
return file_flow_proto_rawDescGZIP(), []int{0}
}
-func (x *FlowEvent) GetEventId() string {
+func (x *FlowEvent) GetEventId() []byte {
if x != nil {
return x.EventId
}
- return ""
+ return nil
}
func (x *FlowEvent) GetTimestamp() *timestamppb.Timestamp {
@@ -204,7 +204,7 @@ type FlowEventAck struct {
unknownFields protoimpl.UnknownFields
// Unique client event identifier that has been ack'ed
- EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
+ EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
}
func (x *FlowEventAck) Reset() {
@@ -239,11 +239,11 @@ func (*FlowEventAck) Descriptor() ([]byte, []int) {
return file_flow_proto_rawDescGZIP(), []int{1}
}
-func (x *FlowEventAck) GetEventId() string {
+func (x *FlowEventAck) GetEventId() []byte {
if x != nil {
return x.EventId
}
- return ""
+ return nil
}
type FlowFields struct {
@@ -278,6 +278,9 @@ type FlowFields struct {
// Number of bytes
RxBytes uint64 `protobuf:"varint,12,opt,name=rx_bytes,json=rxBytes,proto3" json:"rx_bytes,omitempty"`
TxBytes uint64 `protobuf:"varint,13,opt,name=tx_bytes,json=txBytes,proto3" json:"tx_bytes,omitempty"`
+ // Resource ID
+ SourceResourceId []byte `protobuf:"bytes,14,opt,name=source_resource_id,json=sourceResourceId,proto3" json:"source_resource_id,omitempty"`
+ DestResourceId []byte `protobuf:"bytes,15,opt,name=dest_resource_id,json=destResourceId,proto3" json:"dest_resource_id,omitempty"`
}
func (x *FlowFields) Reset() {
@@ -410,6 +413,20 @@ func (x *FlowFields) GetTxBytes() uint64 {
return 0
}
+func (x *FlowFields) GetSourceResourceId() []byte {
+ if x != nil {
+ return x.SourceResourceId
+ }
+ return nil
+}
+
+func (x *FlowFields) GetDestResourceId() []byte {
+ if x != nil {
+ return x.DestResourceId
+ }
+ return nil
+}
+
type isFlowFields_ConnectionInfo interface {
isFlowFields_ConnectionInfo()
}
@@ -548,7 +565,7 @@ var file_flow_proto_rawDesc = []byte{
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09,
+ 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09,
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d,
@@ -559,8 +576,8 @@ var file_flow_proto_rawDesc = []byte{
0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x0a, 0x66, 0x6c,
0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x0c, 0x46, 0x6c, 0x6f, 0x77,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e,
- 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e,
- 0x74, 0x49, 0x64, 0x22, 0xc4, 0x03, 0x0a, 0x0a, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c,
+ 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e,
+ 0x74, 0x49, 0x64, 0x22, 0x9c, 0x04, 0x0a, 0x0a, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c,
0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x66, 0x6c, 0x6f, 0x77,
@@ -587,31 +604,36 @@ var file_flow_proto_rawDesc = []byte{
0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0c, 0x20,
0x01, 0x28, 0x04, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08,
0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07,
- 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
- 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x6f,
- 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
- 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f, 0x75,
- 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x73, 0x74, 0x5f,
- 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x73, 0x74,
- 0x50, 0x6f, 0x72, 0x74, 0x22, 0x44, 0x0a, 0x08, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f,
- 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a,
- 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
- 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x45, 0x0a, 0x04, 0x54, 0x79,
- 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,
- 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41,
- 0x52, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x44,
- 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x10,
- 0x03, 0x2a, 0x3b, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15,
- 0x0a, 0x11, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e,
- 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53,
- 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x32, 0x42,
- 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a,
- 0x06, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46,
- 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e,
- 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x22, 0x00, 0x28, 0x01,
- 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x33,
+ 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x6f, 0x75, 0x72, 0x63,
+ 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20,
+ 0x01, 0x28, 0x0c, 0x52, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75,
+ 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65,
+ 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52,
+ 0x0e, 0x64, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x42,
+ 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e,
+ 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f,
+ 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12,
+ 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x44, 0x0a, 0x08,
+ 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70,
+ 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x63, 0x6d,
+ 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x63, 0x6f,
+ 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x43, 0x6f,
+ 0x64, 0x65, 0x2a, 0x45, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59,
+ 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a,
+ 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08,
+ 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59,
+ 0x50, 0x45, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x03, 0x2a, 0x3b, 0x0a, 0x09, 0x44, 0x69, 0x72,
+ 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54,
+ 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a,
+ 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47,
+ 0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x32, 0x42, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x77, 0x53, 0x65,
+ 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12,
+ 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74,
+ 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e,
+ 0x74, 0x41, 0x63, 0x6b, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/flow/proto/flow.proto b/flow/proto/flow.proto
index 742de82f9..d11af623a 100644
--- a/flow/proto/flow.proto
+++ b/flow/proto/flow.proto
@@ -13,7 +13,7 @@ service FlowService {
message FlowEvent {
// Unique client event identifier
- string event_id = 1;
+ bytes event_id = 1;
// When the event occurred
google.protobuf.Timestamp timestamp = 2;
@@ -26,7 +26,7 @@ message FlowEvent {
message FlowEventAck {
// Unique client event identifier that has been ack'ed
- string event_id = 1;
+ bytes event_id = 1;
}
message FlowFields {
@@ -67,6 +67,11 @@ message FlowFields {
// Number of bytes
uint64 rx_bytes = 12;
uint64 tx_bytes = 13;
+
+ // Resource ID
+ bytes source_resource_id = 14;
+ bytes dest_resource_id = 15;
+
}
// Flow event types
diff --git a/formatter/formatter.go b/formatter/formatter.go
deleted file mode 100644
index 74de38603..000000000
--- a/formatter/formatter.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package formatter
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/sirupsen/logrus"
-)
-
-// TextFormatter formats logs into text with included source code's path
-type TextFormatter struct {
- timestampFormat string
- levelDesc []string
-}
-
-// SyslogFormatter formats logs into text
-type SyslogFormatter struct {
- levelDesc []string
-}
-
-var validLevelDesc = []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG", "TRAC"}
-
-
-// NewTextFormatter create new MyTextFormatter instance
-func NewTextFormatter() *TextFormatter {
- return &TextFormatter{
- levelDesc: validLevelDesc,
- timestampFormat: time.RFC3339, // or RFC3339
- }
-}
-
-// NewSyslogFormatter create new MySyslogFormatter instance
-func NewSyslogFormatter() *SyslogFormatter {
- return &SyslogFormatter{
- levelDesc: validLevelDesc,
- }
-}
-
-// Format renders a single log entry
-func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
- var fields string
- keys := make([]string, 0, len(entry.Data))
- for k, v := range entry.Data {
- if k == "source" {
- continue
- }
- keys = append(keys, fmt.Sprintf("%s: %v", k, v))
- }
-
- if len(keys) > 0 {
- fields = fmt.Sprintf("[%s] ", strings.Join(keys, ", "))
- }
-
- level := f.parseLevel(entry.Level)
-
- return []byte(fmt.Sprintf("%s %s %s%s: %s\n", entry.Time.Format(f.timestampFormat), level, fields, entry.Data["source"], entry.Message)), nil
-}
-
-func (f *TextFormatter) parseLevel(level logrus.Level) string {
- if len(f.levelDesc) < int(level) {
- return ""
- }
-
- return f.levelDesc[level]
-}
-
-// Format renders a single log entry
-func (f *SyslogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
- var fields string
- keys := make([]string, 0, len(entry.Data))
- for k, v := range entry.Data {
- if k == "source" {
- continue
- }
- keys = append(keys, fmt.Sprintf("%s: %v", k, v))
- }
-
- if len(keys) > 0 {
- fields = fmt.Sprintf("[%s] ", strings.Join(keys, ", "))
- }
- return []byte(fmt.Sprintf("%s%s\n", fields, entry.Message)), nil
-}
diff --git a/formatter/hook/additional_empty.go b/formatter/hook/additional_empty.go
new file mode 100644
index 000000000..4f5069482
--- /dev/null
+++ b/formatter/hook/additional_empty.go
@@ -0,0 +1,9 @@
+//go:build !loggoroutine
+
+package hook
+
+import log "github.com/sirupsen/logrus"
+
+func additionalEntries(_ *log.Entry) {
+ // This function is empty and is used to demonstrate the use of additional hooks.
+}
diff --git a/formatter/hook/additional_goroutine.go b/formatter/hook/additional_goroutine.go
new file mode 100644
index 000000000..fb4e09f47
--- /dev/null
+++ b/formatter/hook/additional_goroutine.go
@@ -0,0 +1,12 @@
+//go:build loggoroutine
+
+package hook
+
+import (
+ "github.com/petermattis/goid"
+ log "github.com/sirupsen/logrus"
+)
+
+func additionalEntries(entry *log.Entry) {
+ entry.Data[EntryKeyGoroutineID] = goid.Get()
+}
diff --git a/formatter/hook.go b/formatter/hook/hook.go
similarity index 96%
rename from formatter/hook.go
rename to formatter/hook/hook.go
index 12f27e67d..1b6ceccc9 100644
--- a/formatter/hook.go
+++ b/formatter/hook/hook.go
@@ -1,4 +1,4 @@
-package formatter
+package hook
import (
"fmt"
@@ -41,7 +41,8 @@ func (hook ContextHook) Levels() []logrus.Level {
// Fire extend with the source information the entry.Data
func (hook ContextHook) Fire(entry *logrus.Entry) error {
src := hook.parseSrc(entry.Caller.File)
- entry.Data["source"] = fmt.Sprintf("%s:%v", src, entry.Caller.Line)
+ entry.Data[EntryKeySource] = fmt.Sprintf("%s:%v", src, entry.Caller.Line)
+ additionalEntries(entry)
if entry.Context == nil {
return nil
diff --git a/formatter/hook_test.go b/formatter/hook/hook_test.go
similarity index 98%
rename from formatter/hook_test.go
rename to formatter/hook/hook_test.go
index a4bcb0284..802163244 100644
--- a/formatter/hook_test.go
+++ b/formatter/hook/hook_test.go
@@ -1,4 +1,4 @@
-package formatter
+package hook
import (
"testing"
diff --git a/formatter/hook/keys.go b/formatter/hook/keys.go
new file mode 100644
index 000000000..09781a88b
--- /dev/null
+++ b/formatter/hook/keys.go
@@ -0,0 +1,6 @@
+package hook
+
+const (
+ EntryKeySource = "source"
+ EntryKeyGoroutineID = "goroutine_id"
+)
diff --git a/formatter/levels/levels.go b/formatter/levels/levels.go
new file mode 100644
index 000000000..41ae80db3
--- /dev/null
+++ b/formatter/levels/levels.go
@@ -0,0 +1,3 @@
+package levels
+
+var ValidLevelDesc = []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG", "TRAC"}
diff --git a/formatter/logcat.go b/formatter/logcat/logcat.go
similarity index 63%
rename from formatter/logcat.go
rename to formatter/logcat/logcat.go
index e8f606229..c561d3283 100644
--- a/formatter/logcat.go
+++ b/formatter/logcat/logcat.go
@@ -1,26 +1,28 @@
-package formatter
+package logcat
import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/formatter/levels"
)
-// LogcatFormatter formats logs into text what is fit for logcat
-type LogcatFormatter struct {
+// Formatter formats logs into text what is fit for logcat
+type Formatter struct {
levelDesc []string
}
// NewLogcatFormatter create new LogcatFormatter instance
-func NewLogcatFormatter() *LogcatFormatter {
- return &LogcatFormatter{
- levelDesc: []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG", "TRAC"},
+func NewLogcatFormatter() *Formatter {
+ return &Formatter{
+ levelDesc: levels.ValidLevelDesc,
}
}
// Format renders a single log entry
-func (f *LogcatFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
var fields string
keys := make([]string, 0, len(entry.Data))
for k, v := range entry.Data {
@@ -39,7 +41,7 @@ func (f *LogcatFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return []byte(fmt.Sprintf("[%s] %s%s %s\n", level, fields, entry.Data["source"], entry.Message)), nil
}
-func (f *LogcatFormatter) parseLevel(level logrus.Level) string {
+func (f *Formatter) parseLevel(level logrus.Level) string {
if len(f.levelDesc) < int(level) {
return ""
}
diff --git a/formatter/logcat_test.go b/formatter/logcat/logcat_test.go
similarity index 97%
rename from formatter/logcat_test.go
rename to formatter/logcat/logcat_test.go
index 45ba5bc46..fd4d92881 100644
--- a/formatter/logcat_test.go
+++ b/formatter/logcat/logcat_test.go
@@ -1,4 +1,4 @@
-package formatter
+package logcat
import (
"testing"
@@ -25,4 +25,5 @@ func TestLogcatMessageFormat(t *testing.T) {
if parsedString != expectedString && parsedString != expectedStringVariant {
t.Errorf("The log messages don't match. Expected: '%s', got: '%s'", expectedString, parsedString)
}
+
}
diff --git a/formatter/set.go b/formatter/set.go
index 9dfea5a7f..a609e7b48 100644
--- a/formatter/set.go
+++ b/formatter/set.go
@@ -2,31 +2,37 @@ package formatter
import (
"github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/formatter/hook"
+ "github.com/netbirdio/netbird/formatter/logcat"
+ "github.com/netbirdio/netbird/formatter/syslog"
+ "github.com/netbirdio/netbird/formatter/txt"
)
// SetTextFormatter set the text formatter for given logger.
func SetTextFormatter(logger *logrus.Logger) {
- logger.Formatter = NewTextFormatter()
+ logger.Formatter = txt.NewTextFormatter()
logger.ReportCaller = true
- logger.AddHook(NewContextHook())
+ logger.AddHook(hook.NewContextHook())
}
+
// SetSyslogFormatter set the text formatter for given logger.
func SetSyslogFormatter(logger *logrus.Logger) {
- logger.Formatter = NewSyslogFormatter()
+ logger.Formatter = syslog.NewSyslogFormatter()
logger.ReportCaller = true
- logger.AddHook(NewContextHook())
+ logger.AddHook(hook.NewContextHook())
}
// SetJSONFormatter set the JSON formatter for given logger.
func SetJSONFormatter(logger *logrus.Logger) {
logger.Formatter = &logrus.JSONFormatter{}
logger.ReportCaller = true
- logger.AddHook(NewContextHook())
+ logger.AddHook(hook.NewContextHook())
}
// SetLogcatFormatter set the logcat formatter for given logger.
func SetLogcatFormatter(logger *logrus.Logger) {
- logger.Formatter = NewLogcatFormatter()
+ logger.Formatter = logcat.NewLogcatFormatter()
logger.ReportCaller = true
- logger.AddHook(NewContextHook())
+ logger.AddHook(hook.NewContextHook())
}
diff --git a/formatter/syslog/formatter.go b/formatter/syslog/formatter.go
new file mode 100644
index 000000000..e72c30347
--- /dev/null
+++ b/formatter/syslog/formatter.go
@@ -0,0 +1,39 @@
+package syslog
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/formatter/levels"
+)
+
+// Formatter formats logs into text
+type Formatter struct {
+ levelDesc []string
+}
+
+// NewSyslogFormatter create new MySyslogFormatter instance
+func NewSyslogFormatter() *Formatter {
+ return &Formatter{
+ levelDesc: levels.ValidLevelDesc,
+ }
+}
+
+// Format renders a single log entry
+func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var fields string
+ keys := make([]string, 0, len(entry.Data))
+ for k, v := range entry.Data {
+ if k == "source" {
+ continue
+ }
+ keys = append(keys, fmt.Sprintf("%s: %v", k, v))
+ }
+
+ if len(keys) > 0 {
+ fields = fmt.Sprintf("[%s] ", strings.Join(keys, ", "))
+ }
+ return []byte(fmt.Sprintf("%s%s\n", fields, entry.Message)), nil
+}
diff --git a/formatter/syslog/formatter_test.go b/formatter/syslog/formatter_test.go
new file mode 100644
index 000000000..110a3390b
--- /dev/null
+++ b/formatter/syslog/formatter_test.go
@@ -0,0 +1,26 @@
+package syslog
+
+import (
+ "testing"
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLogSyslogFormat(t *testing.T) {
+
+ someEntry := &logrus.Entry{
+ Data: logrus.Fields{"att1": 1, "att2": 2, "source": "some/fancy/path.go:46"},
+ Time: time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC),
+ Level: 3,
+ Message: "Some Message",
+ }
+
+ formatter := NewSyslogFormatter()
+ result, _ := formatter.Format(someEntry)
+
+ parsedString := string(result)
+ expectedString := "^\\[(att1: 1, att2: 2|att2: 2, att1: 1)\\] Some Message\\s+$"
+ assert.Regexp(t, expectedString, parsedString)
+}
diff --git a/formatter/txt/format.go b/formatter/txt/format.go
new file mode 100644
index 000000000..a88c41044
--- /dev/null
+++ b/formatter/txt/format.go
@@ -0,0 +1,31 @@
+//go:build !loggoroutine
+
+package txt
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/formatter/hook"
+)
+
+func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var fields string
+ keys := make([]string, 0, len(entry.Data))
+ for k, v := range entry.Data {
+ if k == hook.EntryKeySource {
+ continue
+ }
+ keys = append(keys, fmt.Sprintf("%s: %v", k, v))
+ }
+
+ if len(keys) > 0 {
+ fields = fmt.Sprintf("[%s] ", strings.Join(keys, ", "))
+ }
+
+ level := f.parseLevel(entry.Level)
+
+ return []byte(fmt.Sprintf("%s %s %s%s: %s\n", entry.Time.Format(f.timestampFormat), level, fields, entry.Data[hook.EntryKeySource], entry.Message)), nil
+}
diff --git a/formatter/txt/format_gorutines.go b/formatter/txt/format_gorutines.go
new file mode 100644
index 000000000..a39aee633
--- /dev/null
+++ b/formatter/txt/format_gorutines.go
@@ -0,0 +1,35 @@
+//go:build loggoroutine
+
+package txt
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/formatter/hook"
+)
+
+func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var fields string
+ keys := make([]string, 0, len(entry.Data))
+ for k, v := range entry.Data {
+ if k == hook.EntryKeySource {
+ continue
+ }
+
+ if k == hook.EntryKeyGoroutineID {
+ continue
+ }
+ keys = append(keys, fmt.Sprintf("%s: %v", k, v))
+ }
+
+ if len(keys) > 0 {
+ fields = fmt.Sprintf("[%s] ", strings.Join(keys, ", "))
+ }
+
+ level := f.parseLevel(entry.Level)
+
+ return []byte(fmt.Sprintf("%s %s %d %s%s: %s\n", entry.Time.Format(f.timestampFormat), level, entry.Data[hook.EntryKeyGoroutineID], fields, entry.Data[hook.EntryKeySource], entry.Message)), nil
+}
diff --git a/formatter/txt/formatter.go b/formatter/txt/formatter.go
new file mode 100644
index 000000000..3b2a3fb4d
--- /dev/null
+++ b/formatter/txt/formatter.go
@@ -0,0 +1,31 @@
+package txt
+
+import (
+ "time"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/netbirdio/netbird/formatter/levels"
+)
+
+// TextFormatter formats logs into text with included source code's path
+type TextFormatter struct {
+ timestampFormat string
+ levelDesc []string
+}
+
+// NewTextFormatter create new MyTextFormatter instance
+func NewTextFormatter() *TextFormatter {
+ return &TextFormatter{
+ levelDesc: levels.ValidLevelDesc,
+ timestampFormat: time.RFC3339, // or RFC3339
+ }
+}
+
+func (f *TextFormatter) parseLevel(level logrus.Level) string {
+ if len(f.levelDesc) < int(level) {
+ return ""
+ }
+
+ return f.levelDesc[level]
+}
diff --git a/formatter/formatter_test.go b/formatter/txt/formatter_test.go
similarity index 55%
rename from formatter/formatter_test.go
rename to formatter/txt/formatter_test.go
index 1ed207958..590af5d50 100644
--- a/formatter/formatter_test.go
+++ b/formatter/txt/formatter_test.go
@@ -1,4 +1,4 @@
-package formatter
+package txt
import (
"testing"
@@ -24,20 +24,3 @@ func TestLogTextFormat(t *testing.T) {
expectedString := "^2021-02-21T01:10:30Z WARN \\[(att1: 1, att2: 2|att2: 2, att1: 1)\\] some/fancy/path.go:46: Some Message\\s+$"
assert.Regexp(t, expectedString, parsedString)
}
-
-func TestLogSyslogFormat(t *testing.T) {
-
- someEntry := &logrus.Entry{
- Data: logrus.Fields{"att1": 1, "att2": 2, "source": "some/fancy/path.go:46"},
- Time: time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC),
- Level: 3,
- Message: "Some Message",
- }
-
- formatter := NewSyslogFormatter()
- result, _ := formatter.Format(someEntry)
-
- parsedString := string(result)
- expectedString := "^\\[(att1: 1, att2: 2|att2: 2, att1: 1)\\] Some Message\\s+$"
- assert.Regexp(t, expectedString, parsedString)
-}
diff --git a/go.mod b/go.mod
index 62461adc9..9c7e808af 100644
--- a/go.mod
+++ b/go.mod
@@ -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.35.0
- golang.org/x/sys v0.30.0
+ golang.org/x/crypto v0.36.0
+ golang.org/x/sys v0.31.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
@@ -60,11 +60,12 @@ require (
github.com/miekg/dns v1.1.59
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/nadoo/ipset v0.5.0
- github.com/netbirdio/management-integrations/integrations v0.0.0-20250226165736-0ac3dc443266
+ github.com/netbirdio/management-integrations/integrations v0.0.0-20250310094048-24724cc8c9c3
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20241010133937-e0df50df217d
github.com/okta/okta-sdk-golang/v2 v2.18.0
github.com/oschwald/maxminddb-golang v1.12.0
github.com/patrickmn/go-cache v2.1.0+incompatible
+ github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203
github.com/pion/logging v0.2.2
github.com/pion/randutil v0.1.0
github.com/pion/stun/v2 v2.0.0
@@ -96,8 +97,8 @@ require (
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a
golang.org/x/net v0.33.0
golang.org/x/oauth2 v0.19.0
- golang.org/x/sync v0.11.0
- golang.org/x/term v0.29.0
+ golang.org/x/sync v0.12.0
+ golang.org/x/term v0.30.0
google.golang.org/api v0.177.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.7
@@ -231,7 +232,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.22.0 // indirect
+ golang.org/x/text v0.23.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
diff --git a/go.sum b/go.sum
index bd01a035a..75766027d 100644
--- a/go.sum
+++ b/go.sum
@@ -529,8 +529,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e h1:PURA50S8u4mF6RrkYYCAvvPCixhqqEiEy3Ej6avh04c=
github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q=
-github.com/netbirdio/management-integrations/integrations v0.0.0-20250226165736-0ac3dc443266 h1:z7yibtn9dqDpT/UhLYY5bF4ELK5H06d4iaUoteRCfwE=
-github.com/netbirdio/management-integrations/integrations v0.0.0-20250226165736-0ac3dc443266/go.mod h1:smsjbLWt5BUYXRKFwbBvUvembwYYXxryY+EHXKnG5KQ=
+github.com/netbirdio/management-integrations/integrations v0.0.0-20250310094048-24724cc8c9c3 h1:+DNgrPvpdNZR/UiyFQ7fb8weENmy7rB5S54zIoTPhYE=
+github.com/netbirdio/management-integrations/integrations v0.0.0-20250310094048-24724cc8c9c3/go.mod h1:NZ63GQu65YcqarxJxY9p05ukZY16sEmcW9O3GX92T/A=
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20241010133937-e0df50df217d h1:bRq5TKgC7Iq20pDiuC54yXaWnAVeS5PdGpSokFTlR28=
@@ -573,6 +573,8 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs=
+github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA=
github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
@@ -792,8 +794,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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
-golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
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=
@@ -919,8 +921,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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
@@ -993,8 +995,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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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=
@@ -1002,8 +1004,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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
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=
@@ -1018,8 +1020,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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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=
diff --git a/infrastructure_files/docker-compose.yml.tmpl b/infrastructure_files/docker-compose.yml.tmpl
index b7904fb5b..dc491ae23 100644
--- a/infrastructure_files/docker-compose.yml.tmpl
+++ b/infrastructure_files/docker-compose.yml.tmpl
@@ -1,6 +1,5 @@
-version: "3"
services:
- #UI dashboard
+ # UI dashboard
dashboard:
image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
restart: unless-stopped
@@ -33,6 +32,7 @@ services:
options:
max-size: "500m"
max-file: "2"
+
# Signal
signal:
image: netbirdio/signal:$NETBIRD_SIGNAL_TAG
@@ -49,6 +49,7 @@ services:
options:
max-size: "500m"
max-file: "2"
+
# Relay
relay:
image: netbirdio/relay:$NETBIRD_RELAY_TAG
@@ -115,6 +116,7 @@ services:
options:
max-size: "500m"
max-file: "2"
+
volumes:
$MGMT_VOLUMENAME:
$SIGNAL_VOLUMENAME:
diff --git a/infrastructure_files/docker-compose.yml.tmpl.traefik b/infrastructure_files/docker-compose.yml.tmpl.traefik
index dcd3f955c..b62d15b7c 100644
--- a/infrastructure_files/docker-compose.yml.tmpl.traefik
+++ b/infrastructure_files/docker-compose.yml.tmpl.traefik
@@ -1,6 +1,5 @@
-version: "3"
services:
- #UI dashboard
+ # UI dashboard
dashboard:
image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
restart: unless-stopped
@@ -32,6 +31,11 @@ services:
- traefik.enable=true
- traefik.http.routers.netbird-dashboard.rule=Host(`$NETBIRD_DOMAIN`)
- traefik.http.services.netbird-dashboard.loadbalancer.server.port=80
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "500m"
+ max-file: "2"
# Signal
signal:
@@ -40,15 +44,20 @@ services:
volumes:
- $SIGNAL_VOLUMENAME:/var/lib/netbird
#ports:
- # - 10000:80
+ # - $NETBIRD_SIGNAL_PORT:80
# # port and command for Let's Encrypt validation
# - 443:443
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
labels:
- traefik.enable=true
- traefik.http.routers.netbird-signal.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/signalexchange.SignalExchange/`)
- - traefik.http.services.netbird-signal.loadbalancer.server.port=80
+ - traefik.http.services.netbird-signal.loadbalancer.server.port=10000
- traefik.http.services.netbird-signal.loadbalancer.server.scheme=h2c
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "500m"
+ max-file: "2"
# Relay
relay:
@@ -60,8 +69,12 @@ services:
- NB_EXPOSED_ADDRESS=$NETBIRD_RELAY_DOMAIN:$NETBIRD_RELAY_PORT
# todo: change to a secure secret
- NB_AUTH_SECRET=$NETBIRD_RELAY_AUTH_SECRET
- ports:
- - $NETBIRD_RELAY_PORT:$NETBIRD_RELAY_PORT
+ # ports:
+ # - $NETBIRD_RELAY_PORT:$NETBIRD_RELAY_PORT
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.netbird-relay.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/relay`)
+ - traefik.http.services.netbird-relay.loadbalancer.server.port=33080
logging:
driver: "json-file"
options:
@@ -87,8 +100,9 @@ services:
# # command for Let's Encrypt validation without dashboard container
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
command: [
- "--port", "443",
+ "--port", "33073",
"--log-file", "console",
+ "--log-level", "info",
"--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS",
"--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN",
"--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"
@@ -97,12 +111,17 @@ services:
- traefik.enable=true
- traefik.http.routers.netbird-api.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/api`)
- traefik.http.routers.netbird-api.service=netbird-api
- - traefik.http.services.netbird-api.loadbalancer.server.port=443
+ - traefik.http.services.netbird-api.loadbalancer.server.port=33073
- traefik.http.routers.netbird-management.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/management.ManagementService/`)
- traefik.http.routers.netbird-management.service=netbird-management
- - traefik.http.services.netbird-management.loadbalancer.server.port=443
+ - traefik.http.services.netbird-management.loadbalancer.server.port=33073
- traefik.http.services.netbird-management.loadbalancer.server.scheme=h2c
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "500m"
+ max-file: "2"
environment:
- NETBIRD_STORE_ENGINE_POSTGRES_DSN=$NETBIRD_STORE_ENGINE_POSTGRES_DSN
- NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN
@@ -119,6 +138,11 @@ services:
network_mode: host
command:
- -c /etc/turnserver.conf
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "500m"
+ max-file: "2"
volumes:
$MGMT_VOLUMENAME:
diff --git a/infrastructure_files/observability/grafana/dashboards/.gitignore b/infrastructure_files/observability/grafana/dashboards/.gitignore
new file mode 100644
index 000000000..e89fdc9ac
--- /dev/null
+++ b/infrastructure_files/observability/grafana/dashboards/.gitignore
@@ -0,0 +1,2 @@
+# Some files eg. management.json are being ignored by root .gitignore. Need to un-ignore all json dashboards here.
+!*.json
\ No newline at end of file
diff --git a/infrastructure_files/observability/grafana/dashboards/management.json b/infrastructure_files/observability/grafana/dashboards/management.json
new file mode 100644
index 000000000..95983603f
--- /dev/null
+++ b/infrastructure_files/observability/grafana/dashboards/management.json
@@ -0,0 +1,3569 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 1,
+ "id": 82,
+ "links": [],
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of active peer streams connected to the gRPC server",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "blue",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 0
+ },
+ {
+ "color": "#EAB839",
+ "value": 8000
+ },
+ {
+ "color": "green",
+ "value": 11000
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 14,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": true,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "management_grpc_connected_streams_ratio{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}",
+ "format": "time_series",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "Connected grpc streams",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Connected peers",
+ "type": "stat"
+ },
+ {
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 4
+ },
+ "id": 59,
+ "title": "Core metrics / Peers",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of active peer streams connected to the gRPC server over time",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 5
+ },
+ "id": 60,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "management_grpc_connected_streams_ratio{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}",
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}/{{host}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Connected peers count",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Rate of updates with new meta data from the peer.\n\nConstant high numbers of meta updates indicate a version with an issue. This can generate an issue on larger deployments since it will trigger an account-wide network map update.",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 5
+ },
+ "id": 61,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "rate(management_account_peer_meta_update_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Peer metadata updates rate",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 13
+ },
+ "id": 34,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of calculating the peer network map that is sent to the clients.",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 14
+ },
+ "id": 18,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(management_account_get_peer_network_map_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.9,sum(increase(management_account_get_peer_network_map_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(management_account_get_peer_network_map_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "GetPeerNetworkMap Latency",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of triggering the account peers update and preparing the required data for the network map being sent to the clients.\n",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 14
+ },
+ "id": 40,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(management_account_update_account_peers_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.9,sum(increase(management_account_update_account_peers_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(management_account_update_account_peers_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "UpdateAccountPeers Latency",
+ "type": "timeseries"
+ }
+ ],
+ "title": "Core metrics / Network map",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 14
+ },
+ "id": 39,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of update messages piling up in the update channel queue.\n\nA high queue number indicates either a network issue or a high number of account updates.",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 15
+ },
+ "id": 56,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(management_grpc_updatechannel_queue_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.9,sum(increase(management_grpc_updatechannel_queue_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(management_grpc_updatechannel_queue_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "updatechannel queue length",
+ "type": "timeseries"
+ }
+ ],
+ "title": "Core metrics / updatechannel",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 15
+ },
+ "id": 38,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of how long it takes to save or delete an account in the store\n\nWill let you know if the DB is not keeping up with the requests coming from management",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 16
+ },
+ "id": 41,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_store_persistence_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_store_persistence_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_store_persistence_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Database persistence latency",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of how long it takes to execute a transaction in the store\n\nWill let you know if the DB is not keeping up with the requests coming from management",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 16
+ },
+ "id": 42,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_store_transaction_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_store_transaction_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_store_transaction_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Database transaction latency",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of how long a process holds the acquired global lock in the store",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 24
+ },
+ "id": 58,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_store_global_lock_acquisition_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_store_global_lock_acquisition_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_store_global_lock_acquisition_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Global lock duration",
+ "type": "timeseries"
+ }
+ ],
+ "title": "Core metrics / store persistence",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 16
+ },
+ "id": 37,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "A high number for a long period might indicate an inconsistency between the number of local users and IdP",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 17
+ },
+ "id": 47,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "rate(management_idp_authenticate_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "IdP authenticate request rate ",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "A high number for a long period might indicate an inconsistency between the number of local users and IdP",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 17
+ },
+ "id": 48,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "rate(management_idp_get_account_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "IdP get_account request rate ",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqpm"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 25
+ },
+ "id": 49,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "rate(management_idp_update_user_meta_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "IdP update_user_meta request rate ",
+ "type": "timeseries"
+ }
+ ],
+ "title": "Core metrics / IdP",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 17
+ },
+ "id": 36,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 18
+ },
+ "id": 50,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(management_http_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",method=~\"GET|OPTIONS\"}[$__rate_interval])) by (job,method)",
+ "instant": false,
+ "legendFormat": "{{method}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Read request rate (GET, OPTIONS)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 18
+ },
+ "id": 51,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(management_http_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",method=~\"POST|PUT|DELETE\"}[$__rate_interval])) by (job,method)",
+ "instant": false,
+ "legendFormat": "{{method}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Write request rate (PUT, POST,DELETE)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of incoming read HTTP requests (GET, OPTIONS)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 26
+ },
+ "id": 52,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_http_request_duration_ms_total_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",type=~\"read\"}[5m])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_http_request_duration_ms_total_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",type=~\"read\"}[5m])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_http_request_duration_ms_total_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",type=~\"read\"}[5m])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Read request latency (GET, OPTIONS)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of incoming write HTTP requests (PUT, POST,DELETE)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 26
+ },
+ "id": 53,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_http_request_duration_ms_total_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",type=~\"write\"}[5m])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_http_request_duration_ms_total_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",type=~\"write\"}[5m])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_http_request_duration_ms_total_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",type=~\"write\"}[5m])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Write request latency (PUT, POST,DELETE)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 24,
+ "x": 0,
+ "y": 34
+ },
+ "id": 54,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true,
+ "sortBy": "Last *",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(management_http_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (job,exported_endpoint,method)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{method}}-{{exported_endpoint}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Request rate by endpoint",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 41
+ },
+ "id": 55,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true,
+ "sortBy": "Last *",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_http_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[5m])) by (exported_endpoint,method,le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{method}}-{{exported_endpoint}}",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "p90 latency by endpoint",
+ "type": "timeseries"
+ }
+ ],
+ "title": "HTTP metrics",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 18
+ },
+ "id": 35,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Rate of sync gRPC requests from the peers to establish a connection and receive network map updates (update channel)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 18
+ },
+ "id": 43,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(management_grpc_sync_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (job)",
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Sync request rate ",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of the sync gRPC requests from the peers to establish a connection and receive network map updates (update channel)\n\nIf those are to high client will probably retry before management is able to respond\n",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 18
+ },
+ "id": 45,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_grpc_sync_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_grpc_sync_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_grpc_sync_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Sync request latency",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Rate of login gRPC requests from the peers to authenticate and receive initial configuration and relay credentials",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 26
+ },
+ "id": 44,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(management_grpc_login_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (job)",
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Login request rate ",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of the login gRPC requests from the peers to authenticate and receive initial configuration and relay credentials\n\nIf those are to high client will probably retry before management is able to respond\n",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 26
+ },
+ "id": 46,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(management_grpc_login_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(management_grpc_login_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(management_grpc_login_request_duration_ms_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Login request latency",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "GetKey gRPC request rate",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 34
+ },
+ "id": 57,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(management_grpc_key_request_counter_ratio_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (job)",
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "GetKey request rate ",
+ "type": "timeseries"
+ }
+ ],
+ "title": "RPC metrics",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 19
+ },
+ "id": 21,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "fillOpacity": 69,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineWidth": 1,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "fieldMinMax": false,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 51
+ },
+ "id": 11,
+ "options": {
+ "barRadius": 0,
+ "barWidth": 0.97,
+ "fullHighlight": false,
+ "groupWidth": 0.7,
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Mean",
+ "sortDesc": true
+ },
+ "orientation": "auto",
+ "showValue": "auto",
+ "stacking": "normal",
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ },
+ "xTickLabelRotation": 0,
+ "xTickLabelSpacing": 100
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "delta(management_updatechannel_close_one_duration_micro_count{application=\"management\", environment=\"$environment\", host=~\"$host\"}[$interval])",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "CloseOne",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "sum(delta(management_updatechannel_send_duration_micro_count{host=\"$host\", environment=\"$environment\"}[$interval]))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "Send",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "sum(delta(management_updatechannel_create_duration_micro_count{host=\"$host\", environment=\"$environment\"}[$interval]))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "Create",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "delta(management_updatechannel_get_all_duration_micro_count{host=\"$host\", environment=\"$environment\"}[$interval])",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "GetAll",
+ "range": true,
+ "refId": "D",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "delta(management_updatechannel_haschannel_duration_micro_count{host=\"$host\", environment=\"$environment\"}[$interval])",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "HasChannel",
+ "range": true,
+ "refId": "E",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "delta(management_updatechannel_close_multiple_channels_count{host=\"$host\", environment=\"$environment\"}[$interval])",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "CloseMultiple",
+ "range": true,
+ "refId": "F",
+ "useBackend": false
+ }
+ ],
+ "title": "Update Channel operations",
+ "type": "barchart"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of update channel operations by operation",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": true,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "fieldMinMax": false,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "µs"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 51
+ },
+ "id": 13,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "histogram_quantile(0.95, sum by(le) (rate(management_updatechannel_close_one_duration_micro_bucket{application=\"management\", environment=\"$environment\", host=~\"$host\"}[$interval])))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "CloseOne",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum by(le) (rate(management_updatechannel_send_duration_micro_bucket{host=\"$host\", environment=\"$environment\"}[$interval])))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "Send",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum by(le) (rate(management_updatechannel_create_duration_micro_bucket{host=\"$host\", environment=\"$environment\"}[$interval])))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "Create",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum by(le) (rate(management_updatechannel_get_all_duration_micro_bucket{host=\"$host\", environment=\"$environment\"}[$interval])))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "GetAll",
+ "range": true,
+ "refId": "D",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum by(le) (rate(management_updatechannel_haschannel_duration_micro_bucket{host=\"$host\", environment=\"$environment\"}[$interval])))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "CloseMultiple",
+ "range": true,
+ "refId": "E",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum by(le) (rate(management_updatechannel_close_multiple_channels_bucket{host=\"$host\", environment=\"$environment\"}[$interval])))",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "HasChannel",
+ "range": true,
+ "refId": "F",
+ "useBackend": false
+ }
+ ],
+ "title": "Update Channel methods p95 Duration",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Ratio between closed and created update channels",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "axisSoftMax": 100,
+ "axisSoftMin": 0,
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 32,
+ "gradientMode": "hue",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": true,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "line"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "C {__name__=\"management_updatechannel_create_duration_micro_sum\", closed=\"true\", environment=\"production\", host=\"management\", instance=\"localhost:8081\", job=\"prometheus.scrape.local\"}"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Recreation"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 59
+ },
+ "id": 12,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "delta(management_updatechannel_create_duration_micro_count{environment=\"$environment\", closed=\"true\", application=\"management\", host=~\"$host\"}[$interval])",
+ "fullMetaSearch": false,
+ "hide": true,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum by(environment) (delta(management_updatechannel_create_duration_micro_count{environment=\"$environment\", application=\"management\"}[$interval]))",
+ "fullMetaSearch": false,
+ "hide": true,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "__expr__",
+ "uid": "${DS_EXPRESSION}"
+ },
+ "expression": "100-$A/$B*100",
+ "hide": false,
+ "refId": "Rate",
+ "type": "math"
+ }
+ ],
+ "title": "Percentage of Recreated channels",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of update messages piling up in the update channel queue.\n\nA high queue number indicates either a network issue or a high number of account updates.",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "scaleDistribution": {
+ "type": "linear"
+ }
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 59
+ },
+ "id": 27,
+ "options": {
+ "calculate": false,
+ "cellGap": 1,
+ "color": {
+ "exponent": 0.5,
+ "fill": "dark-orange",
+ "mode": "scheme",
+ "reverse": false,
+ "scale": "exponential",
+ "scheme": "Greens",
+ "steps": 73
+ },
+ "exemplars": {
+ "color": "rgba(255,0,255,0.7)"
+ },
+ "filterValues": {
+ "le": 1e-9
+ },
+ "legend": {
+ "show": true
+ },
+ "rowsFrame": {
+ "layout": "auto"
+ },
+ "tooltip": {
+ "mode": "single",
+ "showColorScale": false,
+ "yHistogram": false
+ },
+ "yAxis": {
+ "axisPlacement": "left",
+ "axisWidth": 60,
+ "decimals": 1,
+ "reverse": false,
+ "unit": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum by(le) (increase(management_grpc_updatechannel_queue_bucket{application=\"management\", environment=\"$environment\", host=~\"$host\"}[$__rate_interval]))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Update Channel heat map",
+ "type": "heatmap"
+ }
+ ],
+ "title": "General",
+ "type": "row"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 20
+ },
+ "id": 22,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of triggering the account peers update and preparing the required data for the network map being sent to the clients.\n\nWhen you see a lighter color on a higher timing bucket, your users may experience issues.",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "scaleDistribution": {
+ "type": "linear"
+ }
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 23,
+ "x": 0,
+ "y": 83
+ },
+ "id": 20,
+ "options": {
+ "calculate": false,
+ "cellGap": 1,
+ "color": {
+ "exponent": 0.5,
+ "fill": "dark-orange",
+ "mode": "scheme",
+ "reverse": false,
+ "scale": "exponential",
+ "scheme": "Oranges",
+ "steps": 64
+ },
+ "exemplars": {
+ "color": "rgba(255,0,255,0.7)"
+ },
+ "filterValues": {
+ "le": 1e-9
+ },
+ "legend": {
+ "show": true
+ },
+ "rowsFrame": {
+ "layout": "auto"
+ },
+ "tooltip": {
+ "mode": "single",
+ "showColorScale": false,
+ "yHistogram": false
+ },
+ "yAxis": {
+ "axisPlacement": "left",
+ "axisWidth": 60,
+ "decimals": 1,
+ "reverse": false,
+ "unit": "ms"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum by(le) (increase(management_account_update_account_peers_duration_ms_bucket{application=\"management\", environment=\"$environment\", host=~\"$host\"}[$__rate_interval]))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "UpdateAccountPeers Latency",
+ "type": "heatmap"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of objects in the network map like peers, routes, firewall rules, etc. that are sent to the clients",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "scaleDistribution": {
+ "type": "linear"
+ }
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 23,
+ "x": 0,
+ "y": 91
+ },
+ "id": 19,
+ "options": {
+ "calculate": false,
+ "cellGap": 1,
+ "color": {
+ "exponent": 0.5,
+ "fill": "dark-orange",
+ "mode": "scheme",
+ "reverse": false,
+ "scale": "exponential",
+ "scheme": "Oranges",
+ "steps": 64
+ },
+ "exemplars": {
+ "color": "rgba(255,0,255,0.7)"
+ },
+ "filterValues": {
+ "le": 1e-9
+ },
+ "legend": {
+ "show": true
+ },
+ "rowsFrame": {
+ "layout": "auto"
+ },
+ "tooltip": {
+ "mode": "single",
+ "showColorScale": false,
+ "yHistogram": false
+ },
+ "yAxis": {
+ "axisPlacement": "left",
+ "axisWidth": 60,
+ "decimals": 0,
+ "reverse": false,
+ "unit": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "exemplar": false,
+ "expr": "sum by(le) (increase(management_account_network_map_object_count_bucket{application=\"management\", environment=\"$environment\", host=~\"$host\"}[$__rate_interval]))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "interval": "",
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "NetworkMap Objects",
+ "type": "heatmap"
+ }
+ ],
+ "title": "Network Map",
+ "type": "row"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 39,
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": false,
+ "text": "Prometheus",
+ "value": "73c8e14b-5699-4876-b887-4299930521a5"
+ },
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "datasource",
+ "options": [],
+ "query": "prometheus",
+ "queryValue": "",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "type": "datasource"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(management_grpc_connected_streams_ratio,cluster)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "cluster",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(management_grpc_connected_streams_ratio,cluster)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {
+ "selected": false,
+ "text": "None",
+ "value": ""
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(management_grpc_connected_streams_ratio{cluster=\"$cluster\"},environment)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "environment",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(management_grpc_connected_streams_ratio{cluster=\"$cluster\"},environment)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {
+ "selected": false,
+ "text": "netbird-management",
+ "value": "netbird-management"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(management_grpc_connected_streams_ratio{cluster=\"$cluster\", environment=\"$environment\"},job)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "job",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(management_grpc_connected_streams_ratio{cluster=\"$cluster\", environment=\"$environment\"},job)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(management_grpc_connected_streams_ratio{cluster=\"$cluster\", environment=\"$environment\", job=\"$job\"},host)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "host",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(management_grpc_connected_streams_ratio{cluster=\"$cluster\", environment=\"$environment\", job=\"$job\"},host)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-1h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "browser",
+ "title": "Netbird / Management",
+ "uid": "bebypwoziton4b-v4",
+ "version": 38,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/infrastructure_files/observability/grafana/dashboards/readme.md b/infrastructure_files/observability/grafana/dashboards/readme.md
new file mode 100644
index 000000000..3160921a6
--- /dev/null
+++ b/infrastructure_files/observability/grafana/dashboards/readme.md
@@ -0,0 +1,12 @@
+## Dashboard variables
+
+1. **datasource**: Select Prometheus server
+2. **cluster**: Filter NetBird instances by cluster
+3. **environment**: Filter by environment (dev, staging, UAT, prod)
+4. **job**: Select target NetBird instance if multiple are running
+5. **host**: Filter metrics by host
+
+
+NOTE:
+- Your installation may have a subset of these variables.
+- The dashboard expects `exported_endpoint` instead of `endpoint` in HTTP request metrics.
\ No newline at end of file
diff --git a/infrastructure_files/observability/grafana/dashboards/relay.json b/infrastructure_files/observability/grafana/dashboards/relay.json
new file mode 100644
index 000000000..b80c59743
--- /dev/null
+++ b/infrastructure_files/observability/grafana/dashboards/relay.json
@@ -0,0 +1,926 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 1,
+ "id": 97,
+ "links": [],
+ "panels": [
+ {
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 24,
+ "title": "Core metrics",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of connected peers by host",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 0,
+ "y": 1
+ },
+ "id": 14,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "relay_peers{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}",
+ "format": "time_series",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Connected peers",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of active connected peers by host\n\nIdeally, this number would stay around 20% of the total connections, indicating that most of the connections are P2P",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "continuous-GrYlRd"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "blue",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 0
+ },
+ {
+ "color": "#EAB839",
+ "value": 8000
+ },
+ {
+ "color": "green",
+ "value": 11000
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Idle peers"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "#727374",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 8,
+ "y": 1
+ },
+ "id": 16,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Last *",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "relay_peers_active{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}",
+ "format": "time_series",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ }
+ ],
+ "title": "Active peers",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of idle connected peers by host",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "continuous-RdYlGr"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "blue",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 0
+ },
+ {
+ "color": "#EAB839",
+ "value": 8000
+ },
+ {
+ "color": "green",
+ "value": 11000
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Idle peers"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "#727374",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 16,
+ "y": 1
+ },
+ "id": 19,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Last *",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "relay_peers_idle{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Idle peers",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Authentication latency faced by each relay peer",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 6
+ },
+ "id": 25,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(rate(relay_peer_authentication_time_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.9,sum(rate(relay_peer_authentication_time_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(rate(relay_peer_authentication_time_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Relay peer authentication latency ",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Time taken for storing each peer connection and metadata into in-memory database",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 6
+ },
+ "id": 26,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(rate(relay_peer_store_time_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])) by (le))",
+ "instant": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.9,sum(rate(relay_peer_store_time_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(rate(relay_peer_store_time_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Relay peer store latency ",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Total number of bytes sent/received to peers ",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 36,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 24,
+ "x": 0,
+ "y": 14
+ },
+ "id": 21,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Last *",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "rate(relay_transfer_sent_bytes_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval])",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "sent",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "rate(relay_transfer_received_bytes_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}[$__rate_interval]) *-1",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "received",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ }
+ ],
+ "title": "Relay traffic bandwidth",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 39,
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": true,
+ "text": "Prometheus",
+ "value": "73c8e14b-5699-4876-b887-4299930521a5"
+ },
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "datasource",
+ "options": [],
+ "query": "prometheus",
+ "queryValue": "",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "type": "datasource"
+ },
+ {
+ "current": {
+ "isNone": true,
+ "selected": false,
+ "text": "None",
+ "value": ""
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(relay_peers,cluster)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "cluster",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(relay_peers,cluster)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {
+ "isNone": true,
+ "selected": false,
+ "text": "None",
+ "value": ""
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(relay_peers{cluster=\"$cluster\"},environment)",
+ "description": "",
+ "hide": 0,
+ "includeAll": false,
+ "label": "environment",
+ "multi": false,
+ "name": "environment",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(relay_peers{cluster=\"$cluster\"},environment)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {
+ "selected": false,
+ "text": "netbird-relay",
+ "value": "netbird-relay"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(relay_peers{cluster=\"$cluster\", environment=\"$environment\"},job)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "job",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(relay_peers{cluster=\"$cluster\", environment=\"$environment\"},job)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "allValue": "",
+ "current": {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(relay_peers{cluster=\"$cluster\", environment=\"$environment\", job=\"$job\"},instance)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "host",
+ "multi": true,
+ "name": "host",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(relay_peers{cluster=\"$cluster\", environment=\"$environment\", job=\"$job\"},instance)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "type": "query"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-1h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "browser",
+ "title": "Netbird / Relay",
+ "uid": "febyq2pgq2u-v003",
+ "version": 1,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/infrastructure_files/observability/grafana/dashboards/signal.json b/infrastructure_files/observability/grafana/dashboards/signal.json
new file mode 100644
index 000000000..0dc1b7aa6
--- /dev/null
+++ b/infrastructure_files/observability/grafana/dashboards/signal.json
@@ -0,0 +1,1877 @@
+{
+ "__inputs": [
+ {
+ "name": "DS_PROMETHEUS",
+ "label": "Prometheus",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "prometheus",
+ "pluginName": "Prometheus"
+ }
+ ],
+ "__elements": {},
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "11.1.1"
+ },
+ {
+ "type": "datasource",
+ "id": "prometheus",
+ "name": "Prometheus",
+ "version": "1.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "stat",
+ "name": "Stat",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "timeseries",
+ "name": "Time series",
+ "version": ""
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 1,
+ "id": null,
+ "links": [],
+ "panels": [
+ {
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 13,
+ "title": "Core metrics / peers",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of active connected peers",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 12,
+ "x": 0,
+ "y": 1
+ },
+ "id": 2,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": true,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "sum(active_peers{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\"}) by (cluster,environment,job)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}/",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Total active peers",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of how long a peer was connected\n\nThis is mostly informational, but if most of the connections are short lived, you may need to check any reverse proxy or connectivity issues",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 1
+ },
+ "id": 1,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(peer_connection_duration_seconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90,sum(increase(peer_connection_duration_seconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(peer_connection_duration_seconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "Peer connection duration",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Number of active connected peers by host",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "max": 25000,
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "#EAB839",
+ "value": 15000
+ },
+ {
+ "color": "red",
+ "value": 20000
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": [
+ {
+ "__systemRef": "hideSeriesFrom",
+ "matcher": {
+ "id": "byNames",
+ "options": {
+ "mode": "exclude",
+ "names": [
+ "{__name__=\"active_peers\", application=\"signal\", environment=\"prod\", host=\"signal-1-prod\", instance=\"localhost:9090\", job=\"prometheus.scrape.local\"}"
+ ],
+ "prefix": "All except:",
+ "readOnly": true
+ }
+ },
+ "properties": []
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 12,
+ "x": 0,
+ "y": 5
+ },
+ "id": 10,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "active_peers{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}",
+ "format": "time_series",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}/{{host}}",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Active Peer-Connections per Host",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 9
+ },
+ "id": 14,
+ "panels": [],
+ "title": "Core metrics / messages",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "No. of messages forwarded per seconds. \nThis doesn't include the failed message count. They are tracked separately.",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "mps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 12,
+ "x": 0,
+ "y": 10
+ },
+ "id": 15,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "rate(messages_forwarded_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "instant": false,
+ "legendFormat": "{{cluster}}/{{environment}}/{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Forwarded messages rate",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "mps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 12,
+ "x": 12,
+ "y": 10
+ },
+ "id": 16,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(message_forward_failures_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (cluster,environment,job,type)",
+ "instant": false,
+ "legendFormat": "{{type}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Forwarded messages failure rate",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 15
+ },
+ "id": 17,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(message_forward_latency_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90,sum(increase(message_forward_latency_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(message_forward_latency_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "Forwarded messages latency",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 23
+ },
+ "id": 18,
+ "panels": [],
+ "title": "Core metrics / registrations",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "rate of peer Registration and deregistration",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "cps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 24
+ },
+ "id": 5,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "rate(registrations_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "legendFormat": "registration",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "rate(deregistrations_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "legendFormat": "deregistration",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ }
+ ],
+ "title": "Registration and deregistration rate",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "mps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 24
+ },
+ "id": 19,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(registration_failures_total{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (cluster,environment,job,error)",
+ "instant": false,
+ "legendFormat": "{{error}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Registration failure rate",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "time needed to register new peers",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 32
+ },
+ "id": 20,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(registration_delay_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90,sum(increase(registration_delay_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(registration_delay_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "Peer registration latency",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Duration of how long it takes to load a connection from the registry.\n\nNOTE: It should be below 1 ms",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 32
+ },
+ "id": 21,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(get_registration_delay_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90,sum(increase(get_registration_delay_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(get_registration_delay_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\"}[$__rate_interval])) by (le,cluster,environment,job))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "get_registration request latency",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 40
+ },
+ "id": 12,
+ "panels": [],
+ "title": "RPC metrics",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Send method is used by peers intending to send a message to another peer. The send covers the RPC call of sending the message from the initiator to signal.",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 41
+ },
+ "id": 9,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(rpc_server_requests_per_rpc_count{rpc_method=~\"Send\"}[$__rate_interval])) by (rpc_method,rpc_grpc_status_code)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "code: {{rpc_grpc_status_code}}",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "RPC requests rate (method: Send)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "reqps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 41
+ },
+ "id": 23,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "mean",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.5.0-81732",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(rpc_server_requests_per_rpc_count{rpc_method=~\"ConnectStream\"}[$__rate_interval])) by (rpc_method,rpc_grpc_status_code)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "code: {{rpc_grpc_status_code}}",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "RPC requests rate (method: ConnectStream)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 49
+ },
+ "id": 22,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(rpc_server_duration_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",rpc_method=~\"Send\"}[$__rate_interval])) by (le,cluster,environment,job,rpc_method))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90,sum(increase(rpc_server_duration_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",rpc_method=~\"Send\"}[$__rate_interval])) by (le,cluster,environment,job,rpc_method))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(rpc_server_duration_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",rpc_method=~\"Send\"}[$__rate_interval])) by (le,cluster,environment,job,rpc_method))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "RPC request latency (method: Send)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "description": "Shows estimated time of how long a peer was connected",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 49
+ },
+ "id": 25,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.1.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.5,sum(increase(rpc_server_duration_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",rpc_method=~\"ConnectStream\"}[$__rate_interval])) by (le,cluster,environment,job,rpc_method))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90,sum(increase(rpc_server_duration_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",rpc_method=~\"ConnectStream\"}[$__rate_interval])) by (le,cluster,environment,job,rpc_method))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99,sum(increase(rpc_server_duration_milliseconds_bucket{cluster=~\"$cluster\",environment=~\"$environment\",job=~\"$job\",host=~\"$host\",rpc_method=~\"ConnectStream\"}[$__rate_interval])) by (le,cluster,environment,job,rpc_method))",
+ "format": "heatmap",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "RPC request latency (method: ConnectStream)",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 39,
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {},
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "datasource",
+ "options": [],
+ "query": "prometheus",
+ "queryValue": "",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "type": "datasource"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(active_peers,cluster)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "cluster",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(active_peers,cluster)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(active_peers{cluster=\"$cluster\"},environment)",
+ "description": "",
+ "hide": 0,
+ "includeAll": false,
+ "label": "environment",
+ "multi": false,
+ "name": "environment",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(active_peers{cluster=\"$cluster\"},environment)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(active_peers{cluster=\"$cluster\", environment=\"$environment\"},job)",
+ "hide": 0,
+ "includeAll": false,
+ "multi": false,
+ "name": "job",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(active_peers{cluster=\"$cluster\", environment=\"$environment\"},job)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "allValue": "",
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(active_peers{cluster=\"$cluster\", environment=\"$environment\", job=\"$job\"},host)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "",
+ "multi": true,
+ "name": "host",
+ "options": [],
+ "query": {
+ "qryType": 1,
+ "query": "label_values(active_peers{cluster=\"$cluster\", environment=\"$environment\", job=\"$job\"},host)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "type": "query"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-24h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "browser",
+ "title": "Netbird / Signal",
+ "uid": "cebyq0fs0m-v001",
+ "version": 15,
+ "weekStart": ""
+ }
diff --git a/management/cmd/management.go b/management/cmd/management.go
index 9f8fa2fcb..75d4179cb 100644
--- a/management/cmd/management.go
+++ b/management/cmd/management.go
@@ -37,7 +37,7 @@ import (
"github.com/netbirdio/netbird/management/server/peers"
"github.com/netbirdio/netbird/encryption"
- "github.com/netbirdio/netbird/formatter"
+ "github.com/netbirdio/netbird/formatter/hook"
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/auth"
@@ -91,7 +91,7 @@ var (
flag.Parse()
//nolint
- ctx := context.WithValue(cmd.Context(), formatter.ExecutionContextKey, formatter.SystemSource)
+ ctx := context.WithValue(cmd.Context(), hook.ExecutionContextKey, hook.SystemSource)
err := util.InitLog(logLevel, logFile)
if err != nil {
@@ -137,7 +137,7 @@ var (
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
//nolint
- ctx = context.WithValue(ctx, formatter.ExecutionContextKey, formatter.SystemSource)
+ ctx = context.WithValue(ctx, hook.ExecutionContextKey, hook.SystemSource)
err := handleRebrand(cmd)
if err != nil {
@@ -382,7 +382,7 @@ func unaryInterceptor(
) (interface{}, error) {
reqID := uuid.New().String()
//nolint
- ctx = context.WithValue(ctx, formatter.ExecutionContextKey, formatter.GRPCSource)
+ ctx = context.WithValue(ctx, hook.ExecutionContextKey, hook.GRPCSource)
//nolint
ctx = context.WithValue(ctx, nbContext.RequestIDKey, reqID)
return handler(ctx, req)
@@ -397,7 +397,7 @@ func streamInterceptor(
reqID := uuid.New().String()
wrapped := grpcMiddleware.WrapServerStream(ss)
//nolint
- ctx := context.WithValue(ss.Context(), formatter.ExecutionContextKey, formatter.GRPCSource)
+ ctx := context.WithValue(ss.Context(), hook.ExecutionContextKey, hook.GRPCSource)
//nolint
wrapped.WrappedContext = context.WithValue(ctx, nbContext.RequestIDKey, reqID)
return handler(srv, wrapped)
diff --git a/management/cmd/migration_up.go b/management/cmd/migration_up.go
index 183fc554d..de061dca2 100644
--- a/management/cmd/migration_up.go
+++ b/management/cmd/migration_up.go
@@ -8,7 +8,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
- "github.com/netbirdio/netbird/formatter"
+ "github.com/netbirdio/netbird/formatter/hook"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/util"
)
@@ -30,7 +30,7 @@ var upCmd = &cobra.Command{
}
//nolint
- ctx := context.WithValue(cmd.Context(), formatter.ExecutionContextKey, formatter.SystemSource)
+ ctx := context.WithValue(cmd.Context(), hook.ExecutionContextKey, hook.SystemSource)
if err := store.MigrateFileStoreToSqlite(ctx, mgmtDataDir); err != nil {
return err
diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go
index af5e004a7..e9e0d00e4 100644
--- a/management/proto/management.pb.go
+++ b/management/proto/management.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
-// protoc v4.24.3
+// protoc v3.21.9
// source: management.proto
package proto
diff --git a/management/server/account.go b/management/server/account.go
index eb4f0cf38..b7574f7c1 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -1391,7 +1391,7 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, accountID
peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
defer peerUnlock()
- peer, netMap, postureChecks, err := am.SyncPeer(ctx, account.PeerSync{WireGuardPubKey: peerPubKey, Meta: meta}, accountID)
+ peer, netMap, postureChecks, err := am.SyncPeer(ctx, types.PeerSync{WireGuardPubKey: peerPubKey, Meta: meta}, accountID)
if err != nil {
return nil, nil, nil, fmt.Errorf("error syncing peer: %w", err)
}
@@ -1431,7 +1431,7 @@ func (am *DefaultAccountManager) SyncPeerMeta(ctx context.Context, peerPubKey st
unlockPeer := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
defer unlockPeer()
- _, _, _, err = am.SyncPeer(ctx, account.PeerSync{WireGuardPubKey: peerPubKey, Meta: meta, UpdateAccountPeers: true}, accountID)
+ _, _, _, err = am.SyncPeer(ctx, types.PeerSync{WireGuardPubKey: peerPubKey, Meta: meta, UpdateAccountPeers: true}, accountID)
if err != nil {
return mapError(ctx, err)
}
diff --git a/management/server/account/account_manager_interface.go b/management/server/account/manager.go
similarity index 85%
rename from management/server/account/account_manager_interface.go
rename to management/server/account/manager.go
index f0e20a9d8..9da85473c 100644
--- a/management/server/account/account_manager_interface.go
+++ b/management/server/account/manager.go
@@ -22,8 +22,7 @@ import (
type ExternalCacheManager cache.CacheInterface[*idp.UserData]
-// nolint
-type AccountManager interface {
+type Manager interface {
GetOrCreateAccountByUser(ctx context.Context, userId, domain string) (*types.Account, error)
GetAccount(ctx context.Context, accountID string) (*types.Account, error)
CreateSetupKey(ctx context.Context, accountID string, keyName string, keyType types.SetupKeyType, expiresIn time.Duration,
@@ -89,8 +88,8 @@ type AccountManager interface {
SaveDNSSettings(ctx context.Context, accountID string, userID string, dnsSettingsToSave *types.DNSSettings) error
GetPeer(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error)
UpdateAccountSettings(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Account, error)
- LoginPeer(ctx context.Context, login PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
- SyncPeer(ctx context.Context, sync PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
+ LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
+ SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
GetAllConnectedPeers() (map[string]struct{}, error)
HasConnectedChannel(peerID string) bool
GetExternalCacheManager() ExternalCacheManager
@@ -114,33 +113,3 @@ type AccountManager interface {
SyncUserJWTGroups(ctx context.Context, userAuth nbcontext.UserAuth) error
GetStore() store.Store
}
-
-// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
-type PeerSync struct {
- // WireGuardPubKey is a peers WireGuard public key
- WireGuardPubKey string
- // Meta is the system information passed by peer, must be always present
- Meta nbpeer.PeerSystemMeta
- // UpdateAccountPeers indicate updating account peers,
- // which occurs when the peer's metadata is updated
- UpdateAccountPeers bool
-}
-
-// PeerLogin used as a data object between the gRPC API and AccountManager on Login request.
-type PeerLogin struct {
- // WireGuardPubKey is a peers WireGuard public key
- WireGuardPubKey string
- // SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled)
- SSHKey string
- // Meta is the system information passed by peer, must be always present.
- Meta nbpeer.PeerSystemMeta
- // UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required.
- UserID string
- // SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
- SetupKey string
- // ConnectionIP is the real IP of the peer
- ConnectionIP net.IP
-
- // ExtraDNSLabels is a list of extra DNS labels that the peer wants to use
- ExtraDNSLabels []string
-}
diff --git a/management/server/account_test.go b/management/server/account_test.go
index 7c11048a4..4e47ccc60 100644
--- a/management/server/account_test.go
+++ b/management/server/account_test.go
@@ -38,7 +38,7 @@ import (
"github.com/netbirdio/netbird/route"
)
-func verifyCanAddPeerToAccount(t *testing.T, manager nbAccount.AccountManager, account *types.Account, userID string) {
+func verifyCanAddPeerToAccount(t *testing.T, manager nbAccount.Manager, account *types.Account, userID string) {
t.Helper()
peer := &nbpeer.Peer{
Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=",
@@ -1405,7 +1405,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
}
-func getEvent(t *testing.T, accountID string, manager nbAccount.AccountManager, eventType activity.Activity) *activity.Event {
+func getEvent(t *testing.T, accountID string, manager nbAccount.Manager, eventType activity.Activity) *activity.Event {
t.Helper()
for {
select {
@@ -3026,7 +3026,7 @@ func BenchmarkLoginPeer_ExistingPeer(b *testing.B) {
b.ResetTimer()
start := time.Now()
for i := 0; i < b.N; i++ {
- _, _, _, err := manager.LoginPeer(context.Background(), nbAccount.PeerLogin{
+ _, _, _, err := manager.LoginPeer(context.Background(), types.PeerLogin{
WireGuardPubKey: account.Peers["peer-1"].Key,
SSHKey: "someKey",
Meta: nbpeer.PeerSystemMeta{Hostname: strconv.Itoa(i)},
@@ -3101,7 +3101,7 @@ func BenchmarkLoginPeer_NewPeer(b *testing.B) {
b.ResetTimer()
start := time.Now()
for i := 0; i < b.N; i++ {
- _, _, _, err := manager.LoginPeer(context.Background(), nbAccount.PeerLogin{
+ _, _, _, err := manager.LoginPeer(context.Background(), types.PeerLogin{
WireGuardPubKey: "some-new-key" + strconv.Itoa(i),
SSHKey: "someKey",
Meta: nbpeer.PeerSystemMeta{Hostname: strconv.Itoa(i)},
diff --git a/management/server/ephemeral.go b/management/server/ephemeral.go
index 52ab450d7..3cb9b7536 100644
--- a/management/server/ephemeral.go
+++ b/management/server/ephemeral.go
@@ -35,7 +35,7 @@ type ephemeralPeer struct {
// automatically. Inactivity means the peer disconnected from the Management server.
type EphemeralManager struct {
store store.Store
- accountManager nbAccount.AccountManager
+ accountManager nbAccount.Manager
headPeer *ephemeralPeer
tailPeer *ephemeralPeer
@@ -44,7 +44,7 @@ type EphemeralManager struct {
}
// NewEphemeralManager instantiate new EphemeralManager
-func NewEphemeralManager(store store.Store, accountManager nbAccount.AccountManager) *EphemeralManager {
+func NewEphemeralManager(store store.Store, accountManager nbAccount.Manager) *EphemeralManager {
return &EphemeralManager{
store: store,
accountManager: accountManager,
diff --git a/management/server/ephemeral_test.go b/management/server/ephemeral_test.go
index 76e60f2c6..38477f7a8 100644
--- a/management/server/ephemeral_test.go
+++ b/management/server/ephemeral_test.go
@@ -28,7 +28,7 @@ func (s *MockStore) GetAllEphemeralPeers(_ context.Context, _ store.LockingStren
}
type MocAccountManager struct {
- nbAccount.AccountManager
+ nbAccount.Manager
store *MockStore
}
diff --git a/management/server/groups/manager.go b/management/server/groups/manager.go
index d5431be7b..27698a085 100644
--- a/management/server/groups/manager.go
+++ b/management/server/groups/manager.go
@@ -24,13 +24,13 @@ type Manager interface {
type managerImpl struct {
store store.Store
permissionsManager permissions.Manager
- accountManager account.AccountManager
+ accountManager account.Manager
}
type mockManager struct {
}
-func NewManager(store store.Store, permissionsManager permissions.Manager, accountManager account.AccountManager) Manager {
+func NewManager(store store.Store, permissionsManager permissions.Manager, accountManager account.Manager) Manager {
return &managerImpl{
store: store,
permissionsManager: permissionsManager,
diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go
index 4e72bd96f..97dd7e0b6 100644
--- a/management/server/grpcserver.go
+++ b/management/server/grpcserver.go
@@ -12,13 +12,13 @@ import (
pb "github.com/golang/protobuf/proto" // nolint
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
- "github.com/netbirdio/management-integrations/integrations"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
+ integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
"github.com/netbirdio/netbird/encryption"
"github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/account"
@@ -35,7 +35,7 @@ import (
// GRPCServer an instance of a Management gRPC API server
type GRPCServer struct {
- accountManager account.AccountManager
+ accountManager account.Manager
settingsManager settings.Manager
wgKey wgtypes.Key
proto.UnimplementedManagementServiceServer
@@ -52,7 +52,7 @@ type GRPCServer struct {
func NewServer(
ctx context.Context,
config *Config,
- accountManager account.AccountManager,
+ accountManager account.Manager,
settingsManager settings.Manager,
peersUpdateManager *PeersUpdateManager,
secretsManager SecretsManager,
@@ -460,7 +460,7 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
sshKey = loginReq.GetPeerKeys().GetSshPubKey()
}
- peer, netMap, postureChecks, err := s.accountManager.LoginPeer(ctx, account.PeerLogin{
+ peer, netMap, postureChecks, err := s.accountManager.LoginPeer(ctx, types.PeerLogin{
WireGuardPubKey: peerKey.String(),
SSHKey: string(sshKey),
Meta: extractPeerMeta(ctx, loginReq.GetMeta()),
@@ -605,7 +605,7 @@ func toNetbirdConfig(config *Config, turnCredentials *Token, relayToken *Token,
Relay: relayCfg,
}
- integrations.ExtendNetBirdConfig(nbConfig, extraSettings)
+ integrationsConfig.ExtendNetBirdConfig(nbConfig, extraSettings)
return nbConfig
}
diff --git a/management/server/http/handler.go b/management/server/http/handler.go
index 6a3cfae70..e4cc8585a 100644
--- a/management/server/http/handler.go
+++ b/management/server/http/handler.go
@@ -43,7 +43,7 @@ const apiPrefix = "/api"
// NewAPIHandler creates the Management service HTTP API handler registering all the available endpoints.
func NewAPIHandler(
ctx context.Context,
- accountManager account.AccountManager,
+ accountManager account.Manager,
networksManager nbnetworks.Manager,
resourceManager resources.Manager,
routerManager routers.Manager,
diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go
index b76d28094..054a9e77a 100644
--- a/management/server/http/handlers/accounts/accounts_handler.go
+++ b/management/server/http/handlers/accounts/accounts_handler.go
@@ -18,11 +18,11 @@ import (
// handler is a handler that handles the server.Account HTTP endpoints
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
settingsManager settings.Manager
}
-func AddEndpoints(accountManager account.AccountManager, settingsManager settings.Manager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, settingsManager settings.Manager, router *mux.Router) {
accountsHandler := newHandler(accountManager, settingsManager)
router.HandleFunc("/accounts/{accountId}", accountsHandler.updateAccount).Methods("PUT", "OPTIONS")
router.HandleFunc("/accounts/{accountId}", accountsHandler.deleteAccount).Methods("DELETE", "OPTIONS")
@@ -30,7 +30,7 @@ func AddEndpoints(accountManager account.AccountManager, settingsManager setting
}
// newHandler creates a new handler HTTP handler
-func newHandler(accountManager account.AccountManager, settingsManager settings.Manager) *handler {
+func newHandler(accountManager account.Manager, settingsManager settings.Manager) *handler {
return &handler{
accountManager: accountManager,
settingsManager: settingsManager,
diff --git a/management/server/http/handlers/dns/dns_settings_handler.go b/management/server/http/handlers/dns/dns_settings_handler.go
index 85be8e707..60822c883 100644
--- a/management/server/http/handlers/dns/dns_settings_handler.go
+++ b/management/server/http/handlers/dns/dns_settings_handler.go
@@ -16,22 +16,22 @@ import (
// dnsSettingsHandler is a handler that returns the DNS settings of the account
type dnsSettingsHandler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
addDNSSettingEndpoint(accountManager, router)
addDNSNameserversEndpoint(accountManager, router)
}
-func addDNSSettingEndpoint(accountManager account.AccountManager, router *mux.Router) {
+func addDNSSettingEndpoint(accountManager account.Manager, router *mux.Router) {
dnsSettingsHandler := newDNSSettingsHandler(accountManager)
router.HandleFunc("/dns/settings", dnsSettingsHandler.getDNSSettings).Methods("GET", "OPTIONS")
router.HandleFunc("/dns/settings", dnsSettingsHandler.updateDNSSettings).Methods("PUT", "OPTIONS")
}
// newDNSSettingsHandler returns a new instance of dnsSettingsHandler handler
-func newDNSSettingsHandler(accountManager account.AccountManager) *dnsSettingsHandler {
+func newDNSSettingsHandler(accountManager account.Manager) *dnsSettingsHandler {
return &dnsSettingsHandler{accountManager: accountManager}
}
diff --git a/management/server/http/handlers/dns/nameservers_handler.go b/management/server/http/handlers/dns/nameservers_handler.go
index 0bba95988..970be6d8a 100644
--- a/management/server/http/handlers/dns/nameservers_handler.go
+++ b/management/server/http/handlers/dns/nameservers_handler.go
@@ -18,10 +18,10 @@ import (
// nameserversHandler is the nameserver group handler of the account
type nameserversHandler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func addDNSNameserversEndpoint(accountManager account.AccountManager, router *mux.Router) {
+func addDNSNameserversEndpoint(accountManager account.Manager, router *mux.Router) {
nameserversHandler := newNameserversHandler(accountManager)
router.HandleFunc("/dns/nameservers", nameserversHandler.getAllNameservers).Methods("GET", "OPTIONS")
router.HandleFunc("/dns/nameservers", nameserversHandler.createNameserverGroup).Methods("POST", "OPTIONS")
@@ -31,7 +31,7 @@ func addDNSNameserversEndpoint(accountManager account.AccountManager, router *mu
}
// newNameserversHandler returns a new instance of nameserversHandler handler
-func newNameserversHandler(accountManager account.AccountManager) *nameserversHandler {
+func newNameserversHandler(accountManager account.Manager) *nameserversHandler {
return &nameserversHandler{accountManager: accountManager}
}
diff --git a/management/server/http/handlers/events/events_handler.go b/management/server/http/handlers/events/events_handler.go
index 984201ac0..7497b0fef 100644
--- a/management/server/http/handlers/events/events_handler.go
+++ b/management/server/http/handlers/events/events_handler.go
@@ -17,16 +17,16 @@ import (
// handler HTTP handler
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
eventsHandler := newHandler(accountManager)
router.HandleFunc("/events", eventsHandler.getAllEvents).Methods("GET", "OPTIONS")
}
// newHandler creates a new events handler
-func newHandler(accountManager account.AccountManager) *handler {
+func newHandler(accountManager account.Manager) *handler {
return &handler{accountManager: accountManager}
}
diff --git a/management/server/http/handlers/groups/groups_handler.go b/management/server/http/handlers/groups/groups_handler.go
index 012987994..667095018 100644
--- a/management/server/http/handlers/groups/groups_handler.go
+++ b/management/server/http/handlers/groups/groups_handler.go
@@ -19,10 +19,10 @@ import (
// handler is a handler that returns groups of the account
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
groupsHandler := newHandler(accountManager)
router.HandleFunc("/groups", groupsHandler.getAllGroups).Methods("GET", "OPTIONS")
router.HandleFunc("/groups", groupsHandler.createGroup).Methods("POST", "OPTIONS")
@@ -32,7 +32,7 @@ func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
}
// newHandler creates a new groups handler
-func newHandler(accountManager account.AccountManager) *handler {
+func newHandler(accountManager account.Manager) *handler {
return &handler{
accountManager: accountManager,
}
diff --git a/management/server/http/handlers/networks/handler.go b/management/server/http/handlers/networks/handler.go
index 319b8ca45..1809019a6 100644
--- a/management/server/http/handlers/networks/handler.go
+++ b/management/server/http/handlers/networks/handler.go
@@ -28,12 +28,12 @@ type handler struct {
networksManager networks.Manager
resourceManager resources.Manager
routerManager routers.Manager
- accountManager account.AccountManager
+ accountManager account.Manager
groupsManager groups.Manager
}
-func AddEndpoints(networksManager networks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager groups.Manager, accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(networksManager networks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager groups.Manager, accountManager account.Manager, router *mux.Router) {
addRouterEndpoints(routerManager, router)
addResourceEndpoints(resourceManager, groupsManager, router)
@@ -45,7 +45,7 @@ func AddEndpoints(networksManager networks.Manager, resourceManager resources.Ma
router.HandleFunc("/networks/{networkId}", networksHandler.deleteNetwork).Methods("DELETE", "OPTIONS")
}
-func newHandler(networksManager networks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager groups.Manager, accountManager account.AccountManager) *handler {
+func newHandler(networksManager networks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager groups.Manager, accountManager account.Manager) *handler {
return &handler{
networksManager: networksManager,
resourceManager: resourceManager,
@@ -289,7 +289,7 @@ func (h *handler) collectIDsInNetwork(ctx context.Context, accountID, userID, ne
}
func (h *handler) generateNetworkResponse(networks []*types.Network, routers map[string][]*routerTypes.NetworkRouter, resourceIDs map[string][]string, groups map[string]*nbtypes.Group, account *nbtypes.Account) []*api.Network {
- var networkResponse []*api.Network
+ networkResponse := make([]*api.Network, 0, len(networks))
for _, network := range networks {
routerIDs, peerCounter := getRouterIDs(network, routers, groups)
policyIDs := account.GetPoliciesAppliedInNetwork(network.ID)
diff --git a/management/server/http/handlers/networks/resources_handler.go b/management/server/http/handlers/networks/resources_handler.go
index fba7026e8..4979310ba 100644
--- a/management/server/http/handlers/networks/resources_handler.go
+++ b/management/server/http/handlers/networks/resources_handler.go
@@ -89,7 +89,7 @@ func (h *resourceHandler) getAllResourcesInAccount(w http.ResponseWriter, r *htt
grpsInfoMap := groups.ToGroupsInfoMap(grps, 0)
- var resourcesResponse []*api.NetworkResource
+ resourcesResponse := make([]*api.NetworkResource, 0, len(resources))
for _, resource := range resources {
resourcesResponse = append(resourcesResponse, resource.ToAPIResponse(grpsInfoMap[resource.ID]))
}
diff --git a/management/server/http/handlers/networks/routers_handler.go b/management/server/http/handlers/networks/routers_handler.go
index f98da4966..f1a3fba0b 100644
--- a/management/server/http/handlers/networks/routers_handler.go
+++ b/management/server/http/handlers/networks/routers_handler.go
@@ -48,7 +48,7 @@ func (h *routersHandler) getAllRouters(w http.ResponseWriter, r *http.Request) {
return
}
- var routersResponse []*api.NetworkRouter
+ routersResponse := make([]*api.NetworkRouter, 0, len(routers))
for _, router := range routers {
routersResponse = append(routersResponse, router.ToAPIResponse())
}
diff --git a/management/server/http/handlers/peers/peers_handler.go b/management/server/http/handlers/peers/peers_handler.go
index 4bf6e4830..9342d84a3 100644
--- a/management/server/http/handlers/peers/peers_handler.go
+++ b/management/server/http/handlers/peers/peers_handler.go
@@ -21,10 +21,10 @@ import (
// Handler is a handler that returns peers of the account
type Handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
peersHandler := NewHandler(accountManager)
router.HandleFunc("/peers", peersHandler.GetAllPeers).Methods("GET", "OPTIONS")
router.HandleFunc("/peers/{peerId}", peersHandler.HandlePeer).
@@ -33,7 +33,7 @@ func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
}
// NewHandler creates a new peers Handler
-func NewHandler(accountManager account.AccountManager) *Handler {
+func NewHandler(accountManager account.Manager) *Handler {
return &Handler{
accountManager: accountManager,
}
diff --git a/management/server/http/handlers/policies/geolocations_handler.go b/management/server/http/handlers/policies/geolocations_handler.go
index 818230e44..fb19887dc 100644
--- a/management/server/http/handlers/policies/geolocations_handler.go
+++ b/management/server/http/handlers/policies/geolocations_handler.go
@@ -20,18 +20,18 @@ var (
// geolocationsHandler is a handler that returns locations.
type geolocationsHandler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
geolocationManager geolocation.Geolocation
}
-func addLocationsEndpoint(accountManager account.AccountManager, locationManager geolocation.Geolocation, router *mux.Router) {
+func addLocationsEndpoint(accountManager account.Manager, locationManager geolocation.Geolocation, router *mux.Router) {
locationHandler := newGeolocationsHandlerHandler(accountManager, locationManager)
router.HandleFunc("/locations/countries", locationHandler.getAllCountries).Methods("GET", "OPTIONS")
router.HandleFunc("/locations/countries/{country}/cities", locationHandler.getCitiesByCountry).Methods("GET", "OPTIONS")
}
// newGeolocationsHandlerHandler creates a new Geolocations handler
-func newGeolocationsHandlerHandler(accountManager account.AccountManager, geolocationManager geolocation.Geolocation) *geolocationsHandler {
+func newGeolocationsHandlerHandler(accountManager account.Manager, geolocationManager geolocation.Geolocation) *geolocationsHandler {
return &geolocationsHandler{
accountManager: accountManager,
geolocationManager: geolocationManager,
diff --git a/management/server/http/handlers/policies/policies_handler.go b/management/server/http/handlers/policies/policies_handler.go
index b76f5b1bd..01a09842a 100644
--- a/management/server/http/handlers/policies/policies_handler.go
+++ b/management/server/http/handlers/policies/policies_handler.go
@@ -18,10 +18,10 @@ import (
// handler is a handler that returns policy of the account
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, locationManager geolocation.Geolocation, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, locationManager geolocation.Geolocation, router *mux.Router) {
policiesHandler := newHandler(accountManager)
router.HandleFunc("/policies", policiesHandler.getAllPolicies).Methods("GET", "OPTIONS")
router.HandleFunc("/policies", policiesHandler.createPolicy).Methods("POST", "OPTIONS")
@@ -32,7 +32,7 @@ func AddEndpoints(accountManager account.AccountManager, locationManager geoloca
}
// newHandler creates a new policies handler
-func newHandler(accountManager account.AccountManager) *handler {
+func newHandler(accountManager account.Manager) *handler {
return &handler{
accountManager: accountManager,
}
diff --git a/management/server/http/handlers/policies/posture_checks_handler.go b/management/server/http/handlers/policies/posture_checks_handler.go
index be099d7f2..b99649dbc 100644
--- a/management/server/http/handlers/policies/posture_checks_handler.go
+++ b/management/server/http/handlers/policies/posture_checks_handler.go
@@ -17,11 +17,11 @@ import (
// postureChecksHandler is a handler that returns posture checks of the account.
type postureChecksHandler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
geolocationManager geolocation.Geolocation
}
-func addPostureCheckEndpoint(accountManager account.AccountManager, locationManager geolocation.Geolocation, router *mux.Router) {
+func addPostureCheckEndpoint(accountManager account.Manager, locationManager geolocation.Geolocation, router *mux.Router) {
postureCheckHandler := newPostureChecksHandler(accountManager, locationManager)
router.HandleFunc("/posture-checks", postureCheckHandler.getAllPostureChecks).Methods("GET", "OPTIONS")
router.HandleFunc("/posture-checks", postureCheckHandler.createPostureCheck).Methods("POST", "OPTIONS")
@@ -32,7 +32,7 @@ func addPostureCheckEndpoint(accountManager account.AccountManager, locationMana
}
// newPostureChecksHandler creates a new PostureChecks handler
-func newPostureChecksHandler(accountManager account.AccountManager, geolocationManager geolocation.Geolocation) *postureChecksHandler {
+func newPostureChecksHandler(accountManager account.Manager, geolocationManager geolocation.Geolocation) *postureChecksHandler {
return &postureChecksHandler{
accountManager: accountManager,
geolocationManager: geolocationManager,
diff --git a/management/server/http/handlers/routes/routes_handler.go b/management/server/http/handlers/routes/routes_handler.go
index d6aa52c5e..0f1c37eb7 100644
--- a/management/server/http/handlers/routes/routes_handler.go
+++ b/management/server/http/handlers/routes/routes_handler.go
@@ -21,10 +21,10 @@ const failedToConvertRoute = "failed to convert route to response: %v"
// handler is the routes handler of the account
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
routesHandler := newHandler(accountManager)
router.HandleFunc("/routes", routesHandler.getAllRoutes).Methods("GET", "OPTIONS")
router.HandleFunc("/routes", routesHandler.createRoute).Methods("POST", "OPTIONS")
@@ -34,7 +34,7 @@ func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
}
// newHandler returns a new instance of routes handler
-func newHandler(accountManager account.AccountManager) *handler {
+func newHandler(accountManager account.Manager) *handler {
return &handler{
accountManager: accountManager,
}
diff --git a/management/server/http/handlers/setup_keys/setupkeys_handler.go b/management/server/http/handlers/setup_keys/setupkeys_handler.go
index bebbe1f16..38ba86fb1 100644
--- a/management/server/http/handlers/setup_keys/setupkeys_handler.go
+++ b/management/server/http/handlers/setup_keys/setupkeys_handler.go
@@ -18,10 +18,10 @@ import (
// handler is a handler that returns a list of setup keys of the account
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
keysHandler := newHandler(accountManager)
router.HandleFunc("/setup-keys", keysHandler.getAllSetupKeys).Methods("GET", "OPTIONS")
router.HandleFunc("/setup-keys", keysHandler.createSetupKey).Methods("POST", "OPTIONS")
@@ -31,7 +31,7 @@ func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
}
// newHandler creates a new setup key handler
-func newHandler(accountManager account.AccountManager) *handler {
+func newHandler(accountManager account.Manager) *handler {
return &handler{
accountManager: accountManager,
}
diff --git a/management/server/http/handlers/users/pat_handler.go b/management/server/http/handlers/users/pat_handler.go
index ba8fcd3ed..90913eac1 100644
--- a/management/server/http/handlers/users/pat_handler.go
+++ b/management/server/http/handlers/users/pat_handler.go
@@ -16,10 +16,10 @@ import (
// patHandler is the nameserver group handler of the account
type patHandler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func addUsersTokensEndpoint(accountManager account.AccountManager, router *mux.Router) {
+func addUsersTokensEndpoint(accountManager account.Manager, router *mux.Router) {
tokenHandler := newPATsHandler(accountManager)
router.HandleFunc("/users/{userId}/tokens", tokenHandler.getAllTokens).Methods("GET", "OPTIONS")
router.HandleFunc("/users/{userId}/tokens", tokenHandler.createToken).Methods("POST", "OPTIONS")
@@ -28,7 +28,7 @@ func addUsersTokensEndpoint(accountManager account.AccountManager, router *mux.R
}
// newPATsHandler creates a new patHandler HTTP handler
-func newPATsHandler(accountManager account.AccountManager) *patHandler {
+func newPATsHandler(accountManager account.Manager) *patHandler {
return &patHandler{
accountManager: accountManager,
}
diff --git a/management/server/http/handlers/users/users_handler.go b/management/server/http/handlers/users/users_handler.go
index 6dcd4ea69..19f56c464 100644
--- a/management/server/http/handlers/users/users_handler.go
+++ b/management/server/http/handlers/users/users_handler.go
@@ -19,10 +19,10 @@ import (
// handler is a handler that returns users of the account
type handler struct {
- accountManager account.AccountManager
+ accountManager account.Manager
}
-func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
+func AddEndpoints(accountManager account.Manager, router *mux.Router) {
userHandler := newHandler(accountManager)
router.HandleFunc("/users", userHandler.getAllUsers).Methods("GET", "OPTIONS")
router.HandleFunc("/users/{userId}", userHandler.updateUser).Methods("PUT", "OPTIONS")
@@ -33,7 +33,7 @@ func AddEndpoints(accountManager account.AccountManager, router *mux.Router) {
}
// newHandler creates a new UsersHandler HTTP handler
-func newHandler(accountManager account.AccountManager) *handler {
+func newHandler(accountManager account.Manager) *handler {
return &handler{
accountManager: accountManager,
}
diff --git a/management/server/http/testing/testing_tools/tools.go b/management/server/http/testing/testing_tools/tools.go
index 37b9a3023..5d1498c33 100644
--- a/management/server/http/testing/testing_tools/tools.go
+++ b/management/server/http/testing/testing_tools/tools.go
@@ -94,7 +94,7 @@ type PerformanceMetrics struct {
MaxMsPerOpCICD float64
}
-func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *server.UpdateMessage, validateUpdate bool) (http.Handler, account.AccountManager, chan struct{}) {
+func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *server.UpdateMessage, validateUpdate bool) (http.Handler, account.Manager, chan struct{}) {
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), sqlFile, t.TempDir())
if err != nil {
t.Fatalf("Failed to create test store: %v", err)
diff --git a/management/server/integrations/port_forwarding/controller.go b/management/server/integrations/port_forwarding/controller.go
index 6390868fe..6f062bb12 100644
--- a/management/server/integrations/port_forwarding/controller.go
+++ b/management/server/integrations/port_forwarding/controller.go
@@ -9,6 +9,7 @@ import (
type Controller interface {
SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string)
GetProxyNetworkMaps(ctx context.Context, accountID string) (map[string]*nbtypes.NetworkMap, error)
+ IsPeerInIngressPorts(ctx context.Context, accountID, peerID string) (bool, error)
}
type ControllerMock struct {
@@ -25,3 +26,7 @@ func (c *ControllerMock) SendUpdate(ctx context.Context, accountID string, affec
func (c *ControllerMock) GetProxyNetworkMaps(ctx context.Context, accountID string) (map[string]*nbtypes.NetworkMap, error) {
return make(map[string]*nbtypes.NetworkMap), nil
}
+
+func (c *ControllerMock) IsPeerInIngressPorts(ctx context.Context, accountID, peerID string) (bool, error) {
+ return false, nil
+}
diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go
index 529a3b57f..9bbc9d7a6 100644
--- a/management/server/management_proto_test.go
+++ b/management/server/management_proto_test.go
@@ -20,9 +20,8 @@ import (
"google.golang.org/grpc/keepalive"
"github.com/netbirdio/netbird/encryption"
- "github.com/netbirdio/netbird/formatter"
+ "github.com/netbirdio/netbird/formatter/hook"
mgmtProto "github.com/netbirdio/netbird/management/proto"
- nbAccount "github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/settings"
@@ -426,7 +425,7 @@ func startManagementForTest(t *testing.T, testFile string, config *Config) (*grp
peersUpdateManager := NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{}
- ctx := context.WithValue(context.Background(), formatter.ExecutionContextKey, formatter.SystemSource) //nolint:staticcheck
+ ctx := context.WithValue(context.Background(), hook.ExecutionContextKey, hook.SystemSource) //nolint:staticcheck
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err)
@@ -741,7 +740,7 @@ func Test_LoginPerformance(t *testing.T) {
NetbirdVersion: "",
}
- peerLogin := nbAccount.PeerLogin{
+ peerLogin := types.PeerLogin{
WireGuardPubKey: key.String(),
SSHKey: "random",
Meta: extractPeerMeta(context.Background(), meta),
@@ -766,7 +765,7 @@ func Test_LoginPerformance(t *testing.T) {
messageCalls = append(messageCalls, login)
mu.Unlock()
- go func(peerLogin nbAccount.PeerLogin, counterStart *int32) {
+ go func(peerLogin types.PeerLogin, counterStart *int32) {
defer wgPeer.Done()
_, _, _, err = am.LoginPeer(context.Background(), peerLogin)
if err != nil {
diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go
index e23d198b6..cb8d598f8 100644
--- a/management/server/mock_server/account_mock.go
+++ b/management/server/mock_server/account_mock.go
@@ -22,7 +22,7 @@ import (
"github.com/netbirdio/netbird/route"
)
-var _ account.AccountManager = (*MockAccountManager)(nil)
+var _ account.Manager = (*MockAccountManager)(nil)
type MockAccountManager struct {
GetOrCreateAccountByUserFunc func(ctx context.Context, userId, domain string) (*types.Account, error)
@@ -90,8 +90,8 @@ type MockAccountManager struct {
SaveDNSSettingsFunc func(ctx context.Context, accountID, userID string, dnsSettingsToSave *types.DNSSettings) error
GetPeerFunc func(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error)
UpdateAccountSettingsFunc func(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Account, error)
- LoginPeerFunc func(ctx context.Context, login account.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
- SyncPeerFunc func(ctx context.Context, sync account.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
+ LoginPeerFunc func(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
+ SyncPeerFunc func(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
InviteUserFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserEmail string) error
GetAllConnectedPeersFunc func() (map[string]struct{}, error)
HasConnectedChannelFunc func(peerID string) bool
@@ -663,7 +663,7 @@ func (am *MockAccountManager) UpdateAccountSettings(ctx context.Context, account
}
// LoginPeer mocks LoginPeer of the AccountManager interface
-func (am *MockAccountManager) LoginPeer(ctx context.Context, login account.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
+func (am *MockAccountManager) LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
if am.LoginPeerFunc != nil {
return am.LoginPeerFunc(ctx, login)
}
@@ -671,7 +671,7 @@ func (am *MockAccountManager) LoginPeer(ctx context.Context, login account.PeerL
}
// SyncPeer mocks SyncPeer of the AccountManager interface
-func (am *MockAccountManager) SyncPeer(ctx context.Context, sync account.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
+func (am *MockAccountManager) SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
if am.SyncPeerFunc != nil {
return am.SyncPeerFunc(ctx, sync, accountID)
}
diff --git a/management/server/networks/manager.go b/management/server/networks/manager.go
index 43f0ed0f8..609b68918 100644
--- a/management/server/networks/manager.go
+++ b/management/server/networks/manager.go
@@ -26,7 +26,7 @@ type Manager interface {
type managerImpl struct {
store store.Store
- accountManager account.AccountManager
+ accountManager account.Manager
permissionsManager permissions.Manager
resourcesManager resources.Manager
routersManager routers.Manager
@@ -35,7 +35,7 @@ type managerImpl struct {
type mockManager struct {
}
-func NewManager(store store.Store, permissionsManager permissions.Manager, resourceManager resources.Manager, routersManager routers.Manager, accountManager account.AccountManager) Manager {
+func NewManager(store store.Store, permissionsManager permissions.Manager, resourceManager resources.Manager, routersManager routers.Manager, accountManager account.Manager) Manager {
return &managerImpl{
store: store,
permissionsManager: permissionsManager,
diff --git a/management/server/networks/resources/manager.go b/management/server/networks/resources/manager.go
index 8cf09e561..acaacbfb9 100644
--- a/management/server/networks/resources/manager.go
+++ b/management/server/networks/resources/manager.go
@@ -31,13 +31,13 @@ type managerImpl struct {
store store.Store
permissionsManager permissions.Manager
groupsManager groups.Manager
- accountManager account.AccountManager
+ accountManager account.Manager
}
type mockManager struct {
}
-func NewManager(store store.Store, permissionsManager permissions.Manager, groupsManager groups.Manager, accountManager account.AccountManager) Manager {
+func NewManager(store store.Store, permissionsManager permissions.Manager, groupsManager groups.Manager, accountManager account.Manager) Manager {
return &managerImpl{
store: store,
permissionsManager: permissionsManager,
diff --git a/management/server/networks/routers/manager.go b/management/server/networks/routers/manager.go
index 793efcf4f..595fffd97 100644
--- a/management/server/networks/routers/manager.go
+++ b/management/server/networks/routers/manager.go
@@ -29,13 +29,13 @@ type Manager interface {
type managerImpl struct {
store store.Store
permissionsManager permissions.Manager
- accountManager account.AccountManager
+ accountManager account.Manager
}
type mockManager struct {
}
-func NewManager(store store.Store, permissionsManager permissions.Manager, accountManager account.AccountManager) Manager {
+func NewManager(store store.Store, permissionsManager permissions.Manager, accountManager account.Manager) Manager {
return &managerImpl{
store: store,
permissionsManager: permissionsManager,
diff --git a/management/server/peer.go b/management/server/peer.go
index 0554032ad..4e70fe6e3 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -16,7 +16,6 @@ import (
"golang.org/x/exp/maps"
"github.com/netbirdio/netbird/management/domain"
- "github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/geolocation"
"github.com/netbirdio/netbird/management/server/idp"
@@ -346,6 +345,10 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
return err
}
+ if err = am.validatePeerDelete(ctx, accountID, peerID); err != nil {
+ return err
+ }
+
updateAccountPeers, err = isPeerInActiveGroup(ctx, transaction, accountID, peerID)
if err != nil {
return err
@@ -371,6 +374,9 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
eventsToStore, err = deletePeers(ctx, am, transaction, accountID, userID, []*nbpeer.Peer{peer})
return err
})
+ if err != nil {
+ return err
+ }
for _, storeEvent := range eventsToStore {
storeEvent()
@@ -673,7 +679,7 @@ func getFreeIP(ctx context.Context, transaction store.Store, accountID string) (
}
// SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible
-func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync account.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
+func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
start := time.Now()
defer func() {
log.WithContext(ctx).Debugf("SyncPeer: took %v", time.Since(start))
@@ -748,7 +754,7 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync account.Peer
return am.getValidatedPeerWithMap(ctx, peerNotValid, accountID, peer)
}
-func (am *DefaultAccountManager) handlePeerLoginNotFound(ctx context.Context, login account.PeerLogin, err error) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
+func (am *DefaultAccountManager) handlePeerLoginNotFound(ctx context.Context, login types.PeerLogin, err error) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
// we couldn't find this peer by its public key which can mean that peer hasn't been registered yet.
// Try registering it.
@@ -768,7 +774,7 @@ func (am *DefaultAccountManager) handlePeerLoginNotFound(ctx context.Context, lo
// LoginPeer logs in or registers a peer.
// If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so.
-func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login account.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
+func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, login.WireGuardPubKey)
if err != nil {
return am.handlePeerLoginNotFound(ctx, login, err)
@@ -958,7 +964,7 @@ func processPeerPostureChecks(ctx context.Context, transaction store.Store, poli
// The NetBird client doesn't have a way to check if the peer needs login besides sending a login request
// with no JWT token and usually no setup-key. As the client can send up to two login request to check if it is expired
// and before starting the engine, we do the checks without an account lock to avoid piling up requests.
-func (am *DefaultAccountManager) checkIFPeerNeedsLoginWithoutLock(ctx context.Context, accountID string, login account.PeerLogin) error {
+func (am *DefaultAccountManager) checkIFPeerNeedsLoginWithoutLock(ctx context.Context, accountID string, login types.PeerLogin) error {
peer, err := am.Store.GetPeerByPeerPubKey(ctx, store.LockingStrengthShare, login.WireGuardPubKey)
if err != nil {
return err
@@ -1505,3 +1511,17 @@ func ConvertSliceToMap(existingLabels []string) map[string]struct{} {
}
return labelMap
}
+
+// validatePeerDelete checks if the peer can be deleted.
+func (am *DefaultAccountManager) validatePeerDelete(ctx context.Context, accountId, peerId string) error {
+ linkedInIngressPorts, err := am.proxyController.IsPeerInIngressPorts(ctx, accountId, peerId)
+ if err != nil {
+ return err
+ }
+
+ if linkedInIngressPorts {
+ return status.Errorf(status.PreconditionFailed, "peer is linked to ingress ports: %s", peerId)
+ }
+
+ return nil
+}
diff --git a/management/server/policy.go b/management/server/policy.go
index aea6c5371..6e997a6f0 100644
--- a/management/server/policy.go
+++ b/management/server/policy.go
@@ -256,6 +256,7 @@ func toProtocolFirewallRules(rules []*types.FirewallRule) []*proto.FirewallRule
rule := rules[i]
result[i] = &proto.FirewallRule{
+ PolicyID: []byte(rule.PolicyID),
PeerIP: rule.PeerIP,
Direction: getProtoDirection(rule.Direction),
Action: getProtoAction(rule.Action),
diff --git a/management/server/settings/manager.go b/management/server/settings/manager.go
index 0bf0f63b3..9cc5a0b32 100644
--- a/management/server/settings/manager.go
+++ b/management/server/settings/manager.go
@@ -26,7 +26,8 @@ type managerImpl struct {
}
type ManagerMock struct {
- GetSettingsFunc func(ctx context.Context, accountID, userID string) (*types.Settings, error)
+ GetSettingsFunc func(ctx context.Context, accountID, userID string) (*types.Settings, error)
+ GetExtraSettingsFunc func(ctx context.Context, accountID string) (*types.ExtraSettings, error)
}
func NewManager(store store.Store, userManager users.Manager, extraSettingsManager extra_settings.Manager) Manager {
@@ -117,6 +118,10 @@ func (m *ManagerMock) SetGetSettingsFunc(f func(ctx context.Context, accountID,
}
func (m *ManagerMock) GetExtraSettings(ctx context.Context, accountID string) (*types.ExtraSettings, error) {
+ if m.GetExtraSettingsFunc != nil {
+ return m.GetExtraSettingsFunc(ctx, accountID)
+ }
+
return &types.ExtraSettings{}, nil
}
diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go
index 43697dba4..f7dc20438 100644
--- a/management/server/store/sql_store.go
+++ b/management/server/store/sql_store.go
@@ -2185,6 +2185,7 @@ func (s *SqlStore) GetPeerByIP(ctx context.Context, lockStrength LockingStrength
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).
First(&peer, "account_id = ? AND ip = ?", accountID, jsonValue)
if result.Error != nil {
+ log.WithContext(ctx).Errorf("failed to get peer from the store: %s", result.Error)
return nil, status.Errorf(status.Internal, "failed to get peer from store")
}
diff --git a/management/server/telemetry/http_api_metrics.go b/management/server/telemetry/http_api_metrics.go
index 5ef9e6d02..ae27466d9 100644
--- a/management/server/telemetry/http_api_metrics.go
+++ b/management/server/telemetry/http_api_metrics.go
@@ -13,7 +13,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
- "github.com/netbirdio/netbird/formatter"
+ "github.com/netbirdio/netbird/formatter/hook"
nbContext "github.com/netbirdio/netbird/management/server/context"
)
@@ -167,7 +167,7 @@ func (m *HTTPMiddleware) Handler(h http.Handler) http.Handler {
reqStart := time.Now()
//nolint
- ctx := context.WithValue(r.Context(), formatter.ExecutionContextKey, formatter.HTTPSource)
+ ctx := context.WithValue(r.Context(), hook.ExecutionContextKey, hook.HTTPSource)
reqID := uuid.New().String()
//nolint
diff --git a/management/server/token_mgr.go b/management/server/token_mgr.go
index 7217b543f..2bd59b6e1 100644
--- a/management/server/token_mgr.go
+++ b/management/server/token_mgr.go
@@ -9,13 +9,12 @@ import (
"sync"
"time"
- log "github.com/sirupsen/logrus"
-
- "github.com/netbirdio/management-integrations/integrations"
-
"github.com/netbirdio/netbird/management/proto"
auth "github.com/netbirdio/netbird/relay/auth/hmac"
authv2 "github.com/netbirdio/netbird/relay/auth/hmac/v2"
+ log "github.com/sirupsen/logrus"
+
+ integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
)
const defaultDuration = 12 * time.Hour
@@ -218,7 +217,7 @@ func (m *TimeBasedAuthSecretsManager) pushNewTURNAndRelayTokens(ctx context.Cont
}
}
- integrations.ExtendNetBirdConfig(update.NetbirdConfig, nil)
+ integrationsConfig.ExtendNetBirdConfig(update.NetbirdConfig, nil)
log.WithContext(ctx).Debugf("sending new TURN credentials to peer %s", peerID)
m.updateManager.SendUpdate(ctx, peerID, &UpdateMessage{Update: update})
@@ -242,7 +241,7 @@ func (m *TimeBasedAuthSecretsManager) pushNewRelayTokens(ctx context.Context, pe
},
}
- integrations.ExtendNetBirdConfig(update.NetbirdConfig, nil)
+ integrationsConfig.ExtendNetBirdConfig(update.NetbirdConfig, nil)
log.WithContext(ctx).Debugf("sending new relay credentials to peer %s", peerID)
m.updateManager.SendUpdate(ctx, peerID, &UpdateMessage{Update: update})
diff --git a/management/server/types/peer.go b/management/server/types/peer.go
new file mode 100644
index 000000000..15d343793
--- /dev/null
+++ b/management/server/types/peer.go
@@ -0,0 +1,37 @@
+package types
+
+import (
+ "net"
+
+ nbpeer "github.com/netbirdio/netbird/management/server/peer"
+)
+
+// PeerSync used as a data object between the gRPC API and Manager on Sync request.
+type PeerSync struct {
+ // WireGuardPubKey is a peers WireGuard public key
+ WireGuardPubKey string
+ // Meta is the system information passed by peer, must be always present
+ Meta nbpeer.PeerSystemMeta
+ // UpdateAccountPeers indicate updating account peers,
+ // which occurs when the peer's metadata is updated
+ UpdateAccountPeers bool
+}
+
+// PeerLogin used as a data object between the gRPC API and Manager on Login request.
+type PeerLogin struct {
+ // WireGuardPubKey is a peers WireGuard public key
+ WireGuardPubKey string
+ // SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled)
+ SSHKey string
+ // Meta is the system information passed by peer, must be always present.
+ Meta nbpeer.PeerSystemMeta
+ // UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required.
+ UserID string
+ // SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
+ SetupKey string
+ // ConnectionIP is the real IP of the peer
+ ConnectionIP net.IP
+
+ // ExtraDNSLabels is a list of extra DNS labels that the peer wants to use
+ ExtraDNSLabels []string
+}