mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-25 11:46:40 +00:00
Compare commits
58 Commits
v0.26.4
...
feature/op
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
515ce9e3af | ||
|
|
89383b7f01 | ||
|
|
db34162733 | ||
|
|
bd761e2177 | ||
|
|
4e1b95a4c6 | ||
|
|
05993af7bf | ||
|
|
9d1cb00570 | ||
|
|
543731df45 | ||
|
|
e6628ec231 | ||
|
|
41d4dd2aff | ||
|
|
30bed57711 | ||
|
|
6960b68322 | ||
|
|
3b3aa18148 | ||
|
|
93045f3e3a | ||
|
|
fd3c1dea8e | ||
|
|
48aff7a26e | ||
|
|
83dfe8e3a3 | ||
|
|
38e10af2d9 | ||
|
|
99854a126a | ||
|
|
a75f982fcd | ||
|
|
e7a6483912 | ||
|
|
30ede299b8 | ||
|
|
e3b76448f3 | ||
|
|
e0de86d6c9 | ||
|
|
5204d07811 | ||
|
|
5ea24ba56e | ||
|
|
d30cf8706a | ||
|
|
15a2feb723 | ||
|
|
91b2f9fc51 | ||
|
|
76702c8a09 | ||
|
|
061f673a4f | ||
|
|
9505805313 | ||
|
|
704c67dec8 | ||
|
|
3ed2f08f3c | ||
|
|
4c83408f27 | ||
|
|
90bd39c740 | ||
|
|
dd0cf41147 | ||
|
|
22b2caffc6 | ||
|
|
c1f66d1354 | ||
|
|
ac0fe6025b | ||
|
|
c28657710a | ||
|
|
3875c29f6b | ||
|
|
9f32ccd453 | ||
|
|
1d1d057e7d | ||
|
|
3461b1bb90 | ||
|
|
3d2a2377c6 | ||
|
|
25f5f26527 | ||
|
|
bb0d5c5baf | ||
|
|
7938295190 | ||
|
|
9af532fe71 | ||
|
|
23a1473797 | ||
|
|
9c2dc05df1 | ||
|
|
40d56e5d29 | ||
|
|
fd23d0c28f | ||
|
|
4fff93a1f2 | ||
|
|
22beac1b1b | ||
|
|
bd7a65d798 | ||
|
|
2d76b058fc |
3
.github/workflows/golang-test-darwin.yml
vendored
3
.github/workflows/golang-test-darwin.yml
vendored
@@ -32,6 +32,9 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
macos-go-
|
macos-go-
|
||||||
|
|
||||||
|
- name: Install libpcap
|
||||||
|
run: brew install libpcap
|
||||||
|
|
||||||
- name: Install modules
|
- name: Install modules
|
||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/golang-test-windows.yml
vendored
2
.github/workflows/golang-test-windows.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=C:\Users\runneradmin\AppData\Local\go-build
|
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=C:\Users\runneradmin\AppData\Local\go-build
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: PsExec64 -s -w ${{ github.workspace }} cmd.exe /c "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe test -timeout 5m -p 1 ./... > test-out.txt 2>&1"
|
run: PsExec64 -s -w ${{ github.workspace }} cmd.exe /c "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe test -timeout 10m -p 1 ./... > test-out.txt 2>&1"
|
||||||
- name: test output
|
- name: test output
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: Get-Content test-out.txt
|
run: Get-Content test-out.txt
|
||||||
|
|||||||
4
.github/workflows/golangci-lint.yml
vendored
4
.github/workflows/golangci-lint.yml
vendored
@@ -33,6 +33,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
- name: Check for duplicate constants
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
! awk '/const \(/,/)/{print $0}' management/server/activity/codes.go | grep -o '= [0-9]*' | sort | uniq -d | grep .
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -40,11 +40,12 @@
|
|||||||
|
|
||||||
**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
|
**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
|
||||||
|
|
||||||
**Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
|
**Secure.** NetBird enables secure remote access by applying granular access policies while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
|
||||||
|
|
||||||
### Open-Source Network Security in a Single Platform
|
### Open-Source Network Security in a Single Platform
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
### Key features
|
### Key features
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ Follow the [Advanced guide with a custom identity provider](https://docs.netbird
|
|||||||
- **Public domain** name pointing to the VM.
|
- **Public domain** name pointing to the VM.
|
||||||
|
|
||||||
**Software requirements:**
|
**Software requirements:**
|
||||||
- Docker installed on the VM with the docker compose plugin ([Docker installation guide](https://docs.docker.com/engine/install/)) or docker with docker-compose in version 2 or higher.
|
- Docker installed on the VM with the docker-compose plugin ([Docker installation guide](https://docs.docker.com/engine/install/)) or docker with docker-compose in version 2 or higher.
|
||||||
- [jq](https://jqlang.github.io/jq/) installed. In most distributions
|
- [jq](https://jqlang.github.io/jq/) installed. In most distributions
|
||||||
Usually available in the official repositories and can be installed with `sudo apt install jq` or `sudo yum install jq`
|
Usually available in the official repositories and can be installed with `sudo apt install jq` or `sudo yum install jq`
|
||||||
- [curl](https://curl.se/) installed.
|
- [curl](https://curl.se/) installed.
|
||||||
@@ -93,9 +94,9 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
|
|||||||
- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard.
|
- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard.
|
||||||
- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers).
|
- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers).
|
||||||
- NetBird agent uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between machines.
|
- NetBird agent uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between machines.
|
||||||
- Connection candidates are discovered with a help of [STUN](https://en.wikipedia.org/wiki/STUN) servers.
|
- Connection candidates are discovered with the help of [STUN](https://en.wikipedia.org/wiki/STUN) servers.
|
||||||
- Agents negotiate a connection through [Signal Service](signal/) passing p2p encrypted messages with candidates.
|
- Agents negotiate a connection through [Signal Service](signal/) passing p2p encrypted messages with candidates.
|
||||||
- Sometimes the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT) and p2p connection isn't possible. When this occurs the system falls back to a relay server called [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT), and a secure WireGuard tunnel is established via the TURN server.
|
- Sometimes the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT) and a p2p connection isn't possible. When this occurs the system falls back to a relay server called [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT), and a secure WireGuard tunnel is established via the TURN server.
|
||||||
|
|
||||||
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
|
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ In November 2022, NetBird joined the [StartUpSecure program](https://www.forschu
|
|||||||

|

|
||||||
|
|
||||||
### Testimonials
|
### Testimonials
|
||||||
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
|
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g., by giving a star or a contribution).
|
||||||
|
|
||||||
### Legal
|
### Legal
|
||||||
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const (
|
|||||||
wireguardPortFlag = "wireguard-port"
|
wireguardPortFlag = "wireguard-port"
|
||||||
disableAutoConnectFlag = "disable-auto-connect"
|
disableAutoConnectFlag = "disable-auto-connect"
|
||||||
serverSSHAllowedFlag = "allow-server-ssh"
|
serverSSHAllowedFlag = "allow-server-ssh"
|
||||||
|
extraIFaceBlackListFlag = "extra-iface-blacklist"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -63,6 +64,7 @@ var (
|
|||||||
wireguardPort uint16
|
wireguardPort uint16
|
||||||
serviceName string
|
serviceName string
|
||||||
autoConnectDisabled bool
|
autoConnectDisabled bool
|
||||||
|
extraIFaceBlackList []string
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird",
|
Use: "netbird",
|
||||||
Short: "",
|
Short: "",
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ var installCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
svcConfig.Option["OnFailure"] = "restart"
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(cmd.Context())
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
|
||||||
s, err := newSVC(newProgram(ctx, cancel), svcConfig)
|
s, err := newSVC(newProgram(ctx, cancel), svcConfig)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
clientProto "github.com/netbirdio/netbird/client/proto"
|
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||||
client "github.com/netbirdio/netbird/client/server"
|
client "github.com/netbirdio/netbird/client/server"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
@@ -78,7 +79,8 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false)
|
iv, _ := integrations.NewIntegratedValidator(eventStore)
|
||||||
|
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func init() {
|
|||||||
upCmd.PersistentFlags().BoolVarP(&foregroundMode, "foreground-mode", "F", false, "start service in foreground")
|
upCmd.PersistentFlags().BoolVarP(&foregroundMode, "foreground-mode", "F", false, "start service in foreground")
|
||||||
upCmd.PersistentFlags().StringVar(&interfaceName, interfaceNameFlag, iface.WgInterfaceDefault, "Wireguard interface name")
|
upCmd.PersistentFlags().StringVar(&interfaceName, interfaceNameFlag, iface.WgInterfaceDefault, "Wireguard interface name")
|
||||||
upCmd.PersistentFlags().Uint16Var(&wireguardPort, wireguardPortFlag, iface.DefaultWgPort, "Wireguard interface listening port")
|
upCmd.PersistentFlags().Uint16Var(&wireguardPort, wireguardPortFlag, iface.DefaultWgPort, "Wireguard interface listening port")
|
||||||
|
upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening")
|
||||||
}
|
}
|
||||||
|
|
||||||
func upFunc(cmd *cobra.Command, args []string) error {
|
func upFunc(cmd *cobra.Command, args []string) error {
|
||||||
@@ -83,11 +84,12 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ic := internal.ConfigInput{
|
ic := internal.ConfigInput{
|
||||||
ManagementURL: managementURL,
|
ManagementURL: managementURL,
|
||||||
AdminURL: adminURL,
|
AdminURL: adminURL,
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
NATExternalIPs: natExternalIPs,
|
NATExternalIPs: natExternalIPs,
|
||||||
CustomDNSAddress: customDNSAddressConverted,
|
CustomDNSAddress: customDNSAddressConverted,
|
||||||
|
ExtraIFaceBlackList: extraIFaceBlackList,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Flag(enableRosenpassFlag).Changed {
|
if cmd.Flag(enableRosenpassFlag).Changed {
|
||||||
@@ -149,7 +151,6 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
||||||
|
|
||||||
customDNSAddressConverted, err := parseCustomDNSAddress(cmd.Flag(dnsResolverAddress).Changed)
|
customDNSAddressConverted, err := parseCustomDNSAddress(cmd.Flag(dnsResolverAddress).Changed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -190,6 +191,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
CustomDNSAddress: customDNSAddressConverted,
|
CustomDNSAddress: customDNSAddressConverted,
|
||||||
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
Hostname: hostName,
|
Hostname: hostName,
|
||||||
|
ExtraIFaceBlacklist: extraIFaceBlackList,
|
||||||
}
|
}
|
||||||
|
|
||||||
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ const (
|
|||||||
DefaultAdminURL = "https://app.netbird.io:443"
|
DefaultAdminURL = "https://app.netbird.io:443"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultInterfaceBlacklist = []string{iface.WgInterfaceDefault, "wt", "utun", "tun0", "zt", "ZeroTier", "wg", "ts",
|
var defaultInterfaceBlacklist = []string{
|
||||||
"Tailscale", "tailscale", "docker", "veth", "br-", "lo"}
|
iface.WgInterfaceDefault, "wt", "utun", "tun0", "zt", "ZeroTier", "wg", "ts",
|
||||||
|
"Tailscale", "tailscale", "docker", "veth", "br-", "lo",
|
||||||
|
}
|
||||||
|
|
||||||
// ConfigInput carries configuration changes to the client
|
// ConfigInput carries configuration changes to the client
|
||||||
type ConfigInput struct {
|
type ConfigInput struct {
|
||||||
@@ -47,6 +49,7 @@ type ConfigInput struct {
|
|||||||
InterfaceName *string
|
InterfaceName *string
|
||||||
WireguardPort *int
|
WireguardPort *int
|
||||||
DisableAutoConnect *bool
|
DisableAutoConnect *bool
|
||||||
|
ExtraIFaceBlackList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config Configuration type
|
// Config Configuration type
|
||||||
@@ -220,7 +223,8 @@ func createNewConfig(input ConfigInput) (*Config, error) {
|
|||||||
config.AdminURL = newURL
|
config.AdminURL = newURL
|
||||||
}
|
}
|
||||||
|
|
||||||
config.IFaceBlackList = defaultInterfaceBlacklist
|
// nolint:gocritic
|
||||||
|
config.IFaceBlackList = append(defaultInterfaceBlacklist, input.ExtraIFaceBlackList...)
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +324,13 @@ func update(input ConfigInput) (*Config, error) {
|
|||||||
refresh = true
|
refresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(input.ExtraIFaceBlackList) > 0 {
|
||||||
|
for _, iFace := range util.SliceDiff(input.ExtraIFaceBlackList, config.IFaceBlackList) {
|
||||||
|
config.IFaceBlackList = append(config.IFaceBlackList, iFace)
|
||||||
|
refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if refresh {
|
if refresh {
|
||||||
// since we have new management URL, we need to update config file
|
// since we have new management URL, we need to update config file
|
||||||
if err := util.WriteJson(input.ConfigPath, config); err != nil {
|
if err := util.WriteJson(input.ConfigPath, config); err != nil {
|
||||||
@@ -384,7 +395,6 @@ func configFileIsExists(path string) bool {
|
|||||||
// If it can switch, then it updates the config and returns a new one. Otherwise, it returns the provided config.
|
// If it can switch, then it updates the config and returns a new one. Otherwise, it returns the provided config.
|
||||||
// The check is performed only for the NetBird's managed version.
|
// The check is performed only for the NetBird's managed version.
|
||||||
func UpdateOldManagementURL(ctx context.Context, config *Config, configPath string) (*Config, error) {
|
func UpdateOldManagementURL(ctx context.Context, config *Config, configPath string) (*Config, error) {
|
||||||
|
|
||||||
defaultManagementURL, err := parseURL("Management URL", DefaultManagementURL)
|
defaultManagementURL, err := parseURL("Management URL", DefaultManagementURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ func TestGetConfig(t *testing.T) {
|
|||||||
config, err := UpdateOrCreateConfig(ConfigInput{
|
config, err := UpdateOrCreateConfig(ConfigInput{
|
||||||
ConfigPath: filepath.Join(t.TempDir(), "config.json"),
|
ConfigPath: filepath.Join(t.TempDir(), "config.json"),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -86,6 +85,26 @@ func TestGetConfig(t *testing.T) {
|
|||||||
assert.Equal(t, readConf.(*Config).ManagementURL.String(), newManagementURL)
|
assert.Equal(t, readConf.(*Config).ManagementURL.String(), newManagementURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtraIFaceBlackList(t *testing.T) {
|
||||||
|
extraIFaceBlackList := []string{"eth1"}
|
||||||
|
path := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
config, err := UpdateOrCreateConfig(ConfigInput{
|
||||||
|
ConfigPath: path,
|
||||||
|
ExtraIFaceBlackList: extraIFaceBlackList,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, config.IFaceBlackList, "eth1")
|
||||||
|
readConf, err := util.ReadJson(path, config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, readConf.(*Config).IFaceBlackList, "eth1")
|
||||||
|
}
|
||||||
|
|
||||||
func TestHiddenPreSharedKey(t *testing.T) {
|
func TestHiddenPreSharedKey(t *testing.T) {
|
||||||
hidden := "**********"
|
hidden := "**********"
|
||||||
samplePreSharedKey := "mysecretpresharedkey"
|
samplePreSharedKey := "mysecretpresharedkey"
|
||||||
@@ -111,7 +130,6 @@ func TestHiddenPreSharedKey(t *testing.T) {
|
|||||||
ConfigPath: cfgFile,
|
ConfigPath: cfgFile,
|
||||||
PreSharedKey: tt.preSharedKey,
|
PreSharedKey: tt.preSharedKey,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get cfg: %s", err)
|
t.Fatalf("failed to get cfg: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -93,7 +95,13 @@ func runClient(
|
|||||||
relayProbe *Probe,
|
relayProbe *Probe,
|
||||||
wgProbe *Probe,
|
wgProbe *Probe,
|
||||||
) error {
|
) error {
|
||||||
log.Infof("starting NetBird client version %s", version.NetbirdVersion())
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Panicf("Panic occurred: %v, stack trace: %s", r, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Infof("starting NetBird client version %s on %s/%s", version.NetbirdVersion(), runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
// Check if client was not shut down in a clean way and restore DNS config if required.
|
// Check if client was not shut down in a clean way and restore DNS config if required.
|
||||||
// Otherwise, we might not be able to connect to the management server to retrieve new config.
|
// Otherwise, we might not be able to connect to the management server to retrieve new config.
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ type Engine struct {
|
|||||||
mgmClient mgm.Client
|
mgmClient mgm.Client
|
||||||
// peerConns is a map that holds all the peers that are known to this peer
|
// peerConns is a map that holds all the peers that are known to this peer
|
||||||
peerConns map[string]*peer.Conn
|
peerConns map[string]*peer.Conn
|
||||||
|
|
||||||
|
beforePeerHook peer.BeforeAddPeerHookFunc
|
||||||
|
afterPeerHook peer.AfterRemovePeerHookFunc
|
||||||
|
|
||||||
// rpManager is a Rosenpass manager
|
// rpManager is a Rosenpass manager
|
||||||
rpManager *rosenpass.Manager
|
rpManager *rosenpass.Manager
|
||||||
|
|
||||||
@@ -260,10 +264,14 @@ func (e *Engine) Start() error {
|
|||||||
e.dnsServer = dnsServer
|
e.dnsServer = dnsServer
|
||||||
|
|
||||||
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, initialRoutes)
|
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, initialRoutes)
|
||||||
if err := e.routeManager.Init(); err != nil {
|
beforePeerHook, afterPeerHook, err := e.routeManager.Init()
|
||||||
e.close()
|
if err != nil {
|
||||||
return fmt.Errorf("init route manager: %w", err)
|
log.Errorf("Failed to initialize route manager: %s", err)
|
||||||
|
} else {
|
||||||
|
e.beforePeerHook = beforePeerHook
|
||||||
|
e.afterPeerHook = afterPeerHook
|
||||||
}
|
}
|
||||||
|
|
||||||
e.routeManager.SetRouteChangeListener(e.mobileDep.NetworkChangeListener)
|
e.routeManager.SetRouteChangeListener(e.mobileDep.NetworkChangeListener)
|
||||||
|
|
||||||
err = e.wgInterfaceCreate()
|
err = e.wgInterfaceCreate()
|
||||||
@@ -786,6 +794,7 @@ func (e *Engine) updateOfflinePeers(offlinePeers []*mgmProto.RemotePeerConfig) {
|
|||||||
FQDN: offlinePeer.GetFqdn(),
|
FQDN: offlinePeer.GetFqdn(),
|
||||||
ConnStatus: peer.StatusDisconnected,
|
ConnStatus: peer.StatusDisconnected,
|
||||||
ConnStatusUpdate: time.Now(),
|
ConnStatusUpdate: time.Now(),
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e.statusRecorder.ReplaceOfflinePeers(replacement)
|
e.statusRecorder.ReplaceOfflinePeers(replacement)
|
||||||
@@ -809,10 +818,15 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
|
|||||||
if _, ok := e.peerConns[peerKey]; !ok {
|
if _, ok := e.peerConns[peerKey]; !ok {
|
||||||
conn, err := e.createPeerConn(peerKey, strings.Join(peerIPs, ","))
|
conn, err := e.createPeerConn(peerKey, strings.Join(peerIPs, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("create peer connection: %w", err)
|
||||||
}
|
}
|
||||||
e.peerConns[peerKey] = conn
|
e.peerConns[peerKey] = conn
|
||||||
|
|
||||||
|
if e.beforePeerHook != nil && e.afterPeerHook != nil {
|
||||||
|
conn.AddBeforeAddPeerHook(e.beforePeerHook)
|
||||||
|
conn.AddAfterRemovePeerHook(e.afterPeerHook)
|
||||||
|
}
|
||||||
|
|
||||||
err = e.statusRecorder.AddPeer(peerKey, peerConfig.Fqdn)
|
err = e.statusRecorder.AddPeer(peerKey, peerConfig.Fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err)
|
log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err)
|
||||||
@@ -1106,6 +1120,10 @@ func (e *Engine) close() {
|
|||||||
e.dnsServer.Stop()
|
e.dnsServer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.routeManager != nil {
|
||||||
|
e.routeManager.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("removing Netbird interface %s", e.config.WgIfaceName)
|
log.Debugf("removing Netbird interface %s", e.config.WgIfaceName)
|
||||||
if e.wgInterface != nil {
|
if e.wgInterface != nil {
|
||||||
if err := e.wgInterface.Close(); err != nil {
|
if err := e.wgInterface.Close(); err != nil {
|
||||||
@@ -1120,10 +1138,6 @@ func (e *Engine) close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.routeManager != nil {
|
|
||||||
e.routeManager.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.firewall != nil {
|
if e.firewall != nil {
|
||||||
err := e.firewall.Reset()
|
err := e.firewall.Reset()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
@@ -1050,7 +1051,8 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false)
|
ia, _ := integrations.NewIntegratedValidator(eventStore)
|
||||||
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,15 @@ import (
|
|||||||
"github.com/netbirdio/netbird/iface/bind"
|
"github.com/netbirdio/netbird/iface/bind"
|
||||||
signal "github.com/netbirdio/netbird/signal/client"
|
signal "github.com/netbirdio/netbird/signal/client"
|
||||||
sProto "github.com/netbirdio/netbird/signal/proto"
|
sProto "github.com/netbirdio/netbird/signal/proto"
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
iceKeepAliveDefault = 4 * time.Second
|
iceKeepAliveDefault = 4 * time.Second
|
||||||
iceDisconnectedTimeoutDefault = 6 * time.Second
|
iceDisconnectedTimeoutDefault = 6 * time.Second
|
||||||
|
// iceRelayAcceptanceMinWaitDefault is the same as in the Pion ICE package
|
||||||
|
iceRelayAcceptanceMinWaitDefault = 2 * time.Second
|
||||||
|
|
||||||
defaultWgKeepAlive = 25 * time.Second
|
defaultWgKeepAlive = 25 * time.Second
|
||||||
)
|
)
|
||||||
@@ -98,6 +101,9 @@ type IceCredentials struct {
|
|||||||
Pwd string
|
Pwd string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BeforeAddPeerHookFunc func(connID nbnet.ConnectionID, IP net.IP) error
|
||||||
|
type AfterRemovePeerHookFunc func(connID nbnet.ConnectionID) error
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
config ConnConfig
|
config ConnConfig
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -136,6 +142,10 @@ type Conn struct {
|
|||||||
|
|
||||||
remoteEndpoint *net.UDPAddr
|
remoteEndpoint *net.UDPAddr
|
||||||
remoteConn *ice.Conn
|
remoteConn *ice.Conn
|
||||||
|
|
||||||
|
connID nbnet.ConnectionID
|
||||||
|
beforeAddPeerHooks []BeforeAddPeerHookFunc
|
||||||
|
afterRemovePeerHooks []AfterRemovePeerHookFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// meta holds meta information about a connection
|
// meta holds meta information about a connection
|
||||||
@@ -196,20 +206,22 @@ func (conn *Conn) reCreateAgent() error {
|
|||||||
|
|
||||||
iceKeepAlive := iceKeepAlive()
|
iceKeepAlive := iceKeepAlive()
|
||||||
iceDisconnectedTimeout := iceDisconnectedTimeout()
|
iceDisconnectedTimeout := iceDisconnectedTimeout()
|
||||||
|
iceRelayAcceptanceMinWait := iceRelayAcceptanceMinWait()
|
||||||
|
|
||||||
agentConfig := &ice.AgentConfig{
|
agentConfig := &ice.AgentConfig{
|
||||||
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
||||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4, ice.NetworkTypeUDP6},
|
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4, ice.NetworkTypeUDP6},
|
||||||
Urls: conn.config.StunTurn,
|
Urls: conn.config.StunTurn,
|
||||||
CandidateTypes: conn.candidateTypes(),
|
CandidateTypes: conn.candidateTypes(),
|
||||||
FailedTimeout: &failedTimeout,
|
FailedTimeout: &failedTimeout,
|
||||||
InterfaceFilter: stdnet.InterfaceFilter(conn.config.InterfaceBlackList),
|
InterfaceFilter: stdnet.InterfaceFilter(conn.config.InterfaceBlackList),
|
||||||
UDPMux: conn.config.UDPMux,
|
UDPMux: conn.config.UDPMux,
|
||||||
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
||||||
NAT1To1IPs: conn.config.NATExternalIPs,
|
NAT1To1IPs: conn.config.NATExternalIPs,
|
||||||
Net: transportNet,
|
Net: transportNet,
|
||||||
DisconnectedTimeout: &iceDisconnectedTimeout,
|
DisconnectedTimeout: &iceDisconnectedTimeout,
|
||||||
KeepaliveInterval: &iceKeepAlive,
|
KeepaliveInterval: &iceKeepAlive,
|
||||||
|
RelayAcceptanceMinWait: &iceRelayAcceptanceMinWait,
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.config.DisableIPv6Discovery {
|
if conn.config.DisableIPv6Discovery {
|
||||||
@@ -217,7 +229,6 @@ func (conn *Conn) reCreateAgent() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn.agent, err = ice.NewAgent(agentConfig)
|
conn.agent, err = ice.NewAgent(agentConfig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -273,6 +284,7 @@ func (conn *Conn) Open() error {
|
|||||||
IP: strings.Split(conn.config.WgConfig.AllowedIps, "/")[0],
|
IP: strings.Split(conn.config.WgConfig.AllowedIps, "/")[0],
|
||||||
ConnStatusUpdate: time.Now(),
|
ConnStatusUpdate: time.Now(),
|
||||||
ConnStatus: conn.status,
|
ConnStatus: conn.status,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
err := conn.statusRecorder.UpdatePeerState(peerState)
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -332,6 +344,7 @@ func (conn *Conn) Open() error {
|
|||||||
PubKey: conn.config.Key,
|
PubKey: conn.config.Key,
|
||||||
ConnStatus: conn.status,
|
ConnStatus: conn.status,
|
||||||
ConnStatusUpdate: time.Now(),
|
ConnStatusUpdate: time.Now(),
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
err = conn.statusRecorder.UpdatePeerState(peerState)
|
err = conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -389,6 +402,14 @@ func isRelayCandidate(candidate ice.Candidate) bool {
|
|||||||
return candidate.Type() == ice.CandidateTypeRelay
|
return candidate.Type() == ice.CandidateTypeRelay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) AddBeforeAddPeerHook(hook BeforeAddPeerHookFunc) {
|
||||||
|
conn.beforeAddPeerHooks = append(conn.beforeAddPeerHooks, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) AddAfterRemovePeerHook(hook AfterRemovePeerHookFunc) {
|
||||||
|
conn.afterRemovePeerHooks = append(conn.afterRemovePeerHooks, hook)
|
||||||
|
}
|
||||||
|
|
||||||
// configureConnection starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
// configureConnection starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
||||||
func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, remoteRosenpassPubKey []byte, remoteRosenpassAddr string) (net.Addr, error) {
|
func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, remoteRosenpassPubKey []byte, remoteRosenpassAddr string) (net.Addr, error) {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
@@ -415,6 +436,14 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
|
|||||||
|
|
||||||
endpointUdpAddr, _ := net.ResolveUDPAddr(endpoint.Network(), endpoint.String())
|
endpointUdpAddr, _ := net.ResolveUDPAddr(endpoint.Network(), endpoint.String())
|
||||||
conn.remoteEndpoint = endpointUdpAddr
|
conn.remoteEndpoint = endpointUdpAddr
|
||||||
|
log.Debugf("Conn resolved IP for %s: %s", endpoint, endpointUdpAddr.IP)
|
||||||
|
|
||||||
|
conn.connID = nbnet.GenerateConnID()
|
||||||
|
for _, hook := range conn.beforeAddPeerHooks {
|
||||||
|
if err := hook(conn.connID, endpointUdpAddr.IP); err != nil {
|
||||||
|
log.Errorf("Before add peer hook failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = conn.config.WgConfig.WgInterface.UpdatePeer(conn.config.WgConfig.RemoteKey, conn.config.WgConfig.AllowedIps, defaultWgKeepAlive, endpointUdpAddr, conn.config.WgConfig.PreSharedKey)
|
err = conn.config.WgConfig.WgInterface.UpdatePeer(conn.config.WgConfig.RemoteKey, conn.config.WgConfig.AllowedIps, defaultWgKeepAlive, endpointUdpAddr, conn.config.WgConfig.PreSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -437,9 +466,10 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
|
|||||||
LocalIceCandidateType: pair.Local.Type().String(),
|
LocalIceCandidateType: pair.Local.Type().String(),
|
||||||
RemoteIceCandidateType: pair.Remote.Type().String(),
|
RemoteIceCandidateType: pair.Remote.Type().String(),
|
||||||
LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()),
|
LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()),
|
||||||
RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Local.Port()),
|
RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Remote.Port()),
|
||||||
Direct: !isRelayCandidate(pair.Local),
|
Direct: !isRelayCandidate(pair.Local),
|
||||||
RosenpassEnabled: rosenpassEnabled,
|
RosenpassEnabled: rosenpassEnabled,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
||||||
peerState.Relayed = true
|
peerState.Relayed = true
|
||||||
@@ -506,6 +536,15 @@ func (conn *Conn) cleanup() error {
|
|||||||
// todo: is it problem if we try to remove a peer what is never existed?
|
// todo: is it problem if we try to remove a peer what is never existed?
|
||||||
err3 = conn.config.WgConfig.WgInterface.RemovePeer(conn.config.WgConfig.RemoteKey)
|
err3 = conn.config.WgConfig.WgInterface.RemovePeer(conn.config.WgConfig.RemoteKey)
|
||||||
|
|
||||||
|
if conn.connID != "" {
|
||||||
|
for _, hook := range conn.afterRemovePeerHooks {
|
||||||
|
if err := hook(conn.connID); err != nil {
|
||||||
|
log.Errorf("After remove peer hook failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.connID = ""
|
||||||
|
|
||||||
if conn.notifyDisconnected != nil {
|
if conn.notifyDisconnected != nil {
|
||||||
conn.notifyDisconnected()
|
conn.notifyDisconnected()
|
||||||
conn.notifyDisconnected = nil
|
conn.notifyDisconnected = nil
|
||||||
@@ -521,6 +560,7 @@ func (conn *Conn) cleanup() error {
|
|||||||
PubKey: conn.config.Key,
|
PubKey: conn.config.Key,
|
||||||
ConnStatus: conn.status,
|
ConnStatus: conn.status,
|
||||||
ConnStatusUpdate: time.Now(),
|
ConnStatusUpdate: time.Now(),
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
err := conn.statusRecorder.UpdatePeerState(peerState)
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
envICEKeepAliveIntervalSec = "NB_ICE_KEEP_ALIVE_INTERVAL_SEC"
|
envICEKeepAliveIntervalSec = "NB_ICE_KEEP_ALIVE_INTERVAL_SEC"
|
||||||
envICEDisconnectedTimeoutSec = "NB_ICE_DISCONNECTED_TIMEOUT_SEC"
|
envICEDisconnectedTimeoutSec = "NB_ICE_DISCONNECTED_TIMEOUT_SEC"
|
||||||
envICEForceRelayConn = "NB_ICE_FORCE_RELAY_CONN"
|
envICERelayAcceptanceMinWaitSec = "NB_ICE_RELAY_ACCEPTANCE_MIN_WAIT_SEC"
|
||||||
|
envICEForceRelayConn = "NB_ICE_FORCE_RELAY_CONN"
|
||||||
)
|
)
|
||||||
|
|
||||||
func iceKeepAlive() time.Duration {
|
func iceKeepAlive() time.Duration {
|
||||||
@@ -21,7 +22,7 @@ func iceKeepAlive() time.Duration {
|
|||||||
return iceKeepAliveDefault
|
return iceKeepAliveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("setting ICE keep alive interval to %s seconds", keepAliveEnv)
|
log.Infof("setting ICE keep alive interval to %s seconds", keepAliveEnv)
|
||||||
keepAliveEnvSec, err := strconv.Atoi(keepAliveEnv)
|
keepAliveEnvSec, err := strconv.Atoi(keepAliveEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("invalid value %s set for %s, using default %v", keepAliveEnv, envICEKeepAliveIntervalSec, iceKeepAliveDefault)
|
log.Warnf("invalid value %s set for %s, using default %v", keepAliveEnv, envICEKeepAliveIntervalSec, iceKeepAliveDefault)
|
||||||
@@ -37,7 +38,7 @@ func iceDisconnectedTimeout() time.Duration {
|
|||||||
return iceDisconnectedTimeoutDefault
|
return iceDisconnectedTimeoutDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("setting ICE disconnected timeout to %s seconds", disconnectedTimeoutEnv)
|
log.Infof("setting ICE disconnected timeout to %s seconds", disconnectedTimeoutEnv)
|
||||||
disconnectedTimeoutSec, err := strconv.Atoi(disconnectedTimeoutEnv)
|
disconnectedTimeoutSec, err := strconv.Atoi(disconnectedTimeoutEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("invalid value %s set for %s, using default %v", disconnectedTimeoutEnv, envICEDisconnectedTimeoutSec, iceDisconnectedTimeoutDefault)
|
log.Warnf("invalid value %s set for %s, using default %v", disconnectedTimeoutEnv, envICEDisconnectedTimeoutSec, iceDisconnectedTimeoutDefault)
|
||||||
@@ -47,6 +48,22 @@ func iceDisconnectedTimeout() time.Duration {
|
|||||||
return time.Duration(disconnectedTimeoutSec) * time.Second
|
return time.Duration(disconnectedTimeoutSec) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func iceRelayAcceptanceMinWait() time.Duration {
|
||||||
|
iceRelayAcceptanceMinWaitEnv := os.Getenv(envICERelayAcceptanceMinWaitSec)
|
||||||
|
if iceRelayAcceptanceMinWaitEnv == "" {
|
||||||
|
return iceRelayAcceptanceMinWaitDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("setting ICE relay acceptance min wait to %s seconds", iceRelayAcceptanceMinWaitEnv)
|
||||||
|
disconnectedTimeoutSec, err := strconv.Atoi(iceRelayAcceptanceMinWaitEnv)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("invalid value %s set for %s, using default %v", iceRelayAcceptanceMinWaitEnv, envICERelayAcceptanceMinWaitSec, iceRelayAcceptanceMinWaitDefault)
|
||||||
|
return iceRelayAcceptanceMinWaitDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(disconnectedTimeoutSec) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
func hasICEForceRelayConn() bool {
|
func hasICEForceRelayConn() bool {
|
||||||
disconnectedTimeoutEnv := os.Getenv(envICEForceRelayConn)
|
disconnectedTimeoutEnv := os.Getenv(envICEForceRelayConn)
|
||||||
return strings.ToLower(disconnectedTimeoutEnv) == "true"
|
return strings.ToLower(disconnectedTimeoutEnv) == "true"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
// State contains the latest state of a peer
|
// State contains the latest state of a peer
|
||||||
type State struct {
|
type State struct {
|
||||||
|
Mux *sync.RWMutex
|
||||||
IP string
|
IP string
|
||||||
PubKey string
|
PubKey string
|
||||||
FQDN string
|
FQDN string
|
||||||
@@ -30,7 +31,38 @@ type State struct {
|
|||||||
BytesRx int64
|
BytesRx int64
|
||||||
Latency time.Duration
|
Latency time.Duration
|
||||||
RosenpassEnabled bool
|
RosenpassEnabled bool
|
||||||
Routes map[string]struct{}
|
routes map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRoute add a single route to routes map
|
||||||
|
func (s *State) AddRoute(network string) {
|
||||||
|
s.Mux.Lock()
|
||||||
|
if s.routes == nil {
|
||||||
|
s.routes = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
s.routes[network] = struct{}{}
|
||||||
|
s.Mux.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoutes set state routes
|
||||||
|
func (s *State) SetRoutes(routes map[string]struct{}) {
|
||||||
|
s.Mux.Lock()
|
||||||
|
s.routes = routes
|
||||||
|
s.Mux.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRoute removes a route from the network amp
|
||||||
|
func (s *State) DeleteRoute(network string) {
|
||||||
|
s.Mux.Lock()
|
||||||
|
delete(s.routes, network)
|
||||||
|
s.Mux.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoutes return routes map
|
||||||
|
func (s *State) GetRoutes() map[string]struct{} {
|
||||||
|
s.Mux.RLock()
|
||||||
|
defer s.Mux.RUnlock()
|
||||||
|
return s.routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalPeerState contains the latest state of the local peer
|
// LocalPeerState contains the latest state of the local peer
|
||||||
@@ -143,6 +175,7 @@ func (d *Status) AddPeer(peerPubKey string, fqdn string) error {
|
|||||||
PubKey: peerPubKey,
|
PubKey: peerPubKey,
|
||||||
ConnStatus: StatusDisconnected,
|
ConnStatus: StatusDisconnected,
|
||||||
FQDN: fqdn,
|
FQDN: fqdn,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
d.peerListChangedForNotification = true
|
d.peerListChangedForNotification = true
|
||||||
return nil
|
return nil
|
||||||
@@ -189,8 +222,8 @@ func (d *Status) UpdatePeerState(receivedState State) error {
|
|||||||
peerState.IP = receivedState.IP
|
peerState.IP = receivedState.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
if receivedState.Routes != nil {
|
if receivedState.GetRoutes() != nil {
|
||||||
peerState.Routes = receivedState.Routes
|
peerState.SetRoutes(receivedState.GetRoutes())
|
||||||
}
|
}
|
||||||
|
|
||||||
skipNotification := shouldSkipNotify(receivedState, peerState)
|
skipNotification := shouldSkipNotify(receivedState, peerState)
|
||||||
@@ -440,7 +473,6 @@ func (d *Status) IsLoginRequired() bool {
|
|||||||
s, ok := gstatus.FromError(d.managementError)
|
s, ok := gstatus.FromError(d.managementError)
|
||||||
if ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
|
if ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package peer
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -42,6 +43,7 @@ func TestUpdatePeerState(t *testing.T) {
|
|||||||
status := NewRecorder("https://mgm")
|
status := NewRecorder("https://mgm")
|
||||||
peerState := State{
|
peerState := State{
|
||||||
PubKey: key,
|
PubKey: key,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
|
|
||||||
status.peers[key] = peerState
|
status.peers[key] = peerState
|
||||||
@@ -62,6 +64,7 @@ func TestStatus_UpdatePeerFQDN(t *testing.T) {
|
|||||||
status := NewRecorder("https://mgm")
|
status := NewRecorder("https://mgm")
|
||||||
peerState := State{
|
peerState := State{
|
||||||
PubKey: key,
|
PubKey: key,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
|
|
||||||
status.peers[key] = peerState
|
status.peers[key] = peerState
|
||||||
@@ -80,6 +83,7 @@ func TestGetPeerStateChangeNotifierLogic(t *testing.T) {
|
|||||||
status := NewRecorder("https://mgm")
|
status := NewRecorder("https://mgm")
|
||||||
peerState := State{
|
peerState := State{
|
||||||
PubKey: key,
|
PubKey: key,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
|
|
||||||
status.peers[key] = peerState
|
status.peers[key] = peerState
|
||||||
@@ -104,6 +108,7 @@ func TestRemovePeer(t *testing.T) {
|
|||||||
status := NewRecorder("https://mgm")
|
status := NewRecorder("https://mgm")
|
||||||
peerState := State{
|
peerState := State{
|
||||||
PubKey: key,
|
PubKey: key,
|
||||||
|
Mux: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
|
|
||||||
status.peers[key] = peerState
|
status.peers[key] = peerState
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ type routerPeerStatus struct {
|
|||||||
connected bool
|
connected bool
|
||||||
relayed bool
|
relayed bool
|
||||||
direct bool
|
direct bool
|
||||||
|
latency time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type routesUpdate struct {
|
type routesUpdate struct {
|
||||||
@@ -68,6 +70,7 @@ func (c *clientNetwork) getRouterPeerStatuses() map[string]routerPeerStatus {
|
|||||||
connected: peerStatus.ConnStatus == peer.StatusConnected,
|
connected: peerStatus.ConnStatus == peer.StatusConnected,
|
||||||
relayed: peerStatus.Relayed,
|
relayed: peerStatus.Relayed,
|
||||||
direct: peerStatus.Direct,
|
direct: peerStatus.Direct,
|
||||||
|
latency: peerStatus.Latency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return routePeerStatuses
|
return routePeerStatuses
|
||||||
@@ -83,11 +86,13 @@ func (c *clientNetwork) getRouterPeerStatuses() map[string]routerPeerStatus {
|
|||||||
// * Non-relayed: Routes without relays are preferred.
|
// * Non-relayed: Routes without relays are preferred.
|
||||||
// * Direct connections: Routes with direct peer connections are favored.
|
// * Direct connections: Routes with direct peer connections are favored.
|
||||||
// * Stability: In case of equal scores, the currently active route (if any) is maintained.
|
// * Stability: In case of equal scores, the currently active route (if any) is maintained.
|
||||||
|
// * Latency: Routes with lower latency are prioritized.
|
||||||
//
|
//
|
||||||
// It returns the ID of the selected optimal route.
|
// It returns the ID of the selected optimal route.
|
||||||
func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]routerPeerStatus) string {
|
func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]routerPeerStatus) string {
|
||||||
chosen := ""
|
chosen := ""
|
||||||
chosenScore := 0
|
chosenScore := float64(0)
|
||||||
|
currScore := float64(0)
|
||||||
|
|
||||||
currID := ""
|
currID := ""
|
||||||
if c.chosenRoute != nil {
|
if c.chosenRoute != nil {
|
||||||
@@ -95,7 +100,7 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range c.routes {
|
for _, r := range c.routes {
|
||||||
tempScore := 0
|
tempScore := float64(0)
|
||||||
peerStatus, found := routePeerStatuses[r.ID]
|
peerStatus, found := routePeerStatuses[r.ID]
|
||||||
if !found || !peerStatus.connected {
|
if !found || !peerStatus.connected {
|
||||||
continue
|
continue
|
||||||
@@ -103,9 +108,18 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro
|
|||||||
|
|
||||||
if r.Metric < route.MaxMetric {
|
if r.Metric < route.MaxMetric {
|
||||||
metricDiff := route.MaxMetric - r.Metric
|
metricDiff := route.MaxMetric - r.Metric
|
||||||
tempScore = metricDiff * 10
|
tempScore = float64(metricDiff) * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in some temporal cases, latency can be 0, so we set it to 1s to not block but try to avoid this route
|
||||||
|
latency := time.Second
|
||||||
|
if peerStatus.latency != 0 {
|
||||||
|
latency = peerStatus.latency
|
||||||
|
} else {
|
||||||
|
log.Warnf("peer %s has 0 latency", r.Peer)
|
||||||
|
}
|
||||||
|
tempScore += 1 - latency.Seconds()
|
||||||
|
|
||||||
if !peerStatus.relayed {
|
if !peerStatus.relayed {
|
||||||
tempScore++
|
tempScore++
|
||||||
}
|
}
|
||||||
@@ -114,7 +128,7 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro
|
|||||||
tempScore++
|
tempScore++
|
||||||
}
|
}
|
||||||
|
|
||||||
if tempScore > chosenScore || (tempScore == chosenScore && r.ID == currID) {
|
if tempScore > chosenScore || (tempScore == chosenScore && chosen == "") {
|
||||||
chosen = r.ID
|
chosen = r.ID
|
||||||
chosenScore = tempScore
|
chosenScore = tempScore
|
||||||
}
|
}
|
||||||
@@ -123,18 +137,26 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro
|
|||||||
chosen = r.ID
|
chosen = r.ID
|
||||||
chosenScore = tempScore
|
chosenScore = tempScore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.ID == currID {
|
||||||
|
currScore = tempScore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chosen == "" {
|
switch {
|
||||||
|
case chosen == "":
|
||||||
var peers []string
|
var peers []string
|
||||||
for _, r := range c.routes {
|
for _, r := range c.routes {
|
||||||
peers = append(peers, r.Peer)
|
peers = append(peers, r.Peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warnf("the network %s has not been assigned a routing peer as no peers from the list %s are currently connected", c.network, peers)
|
log.Warnf("the network %s has not been assigned a routing peer as no peers from the list %s are currently connected", c.network, peers)
|
||||||
|
case chosen != currID:
|
||||||
} else if chosen != currID {
|
if currScore != 0 && currScore < chosenScore+0.1 {
|
||||||
log.Infof("new chosen route is %s with peer %s with score %d for network %s", chosen, c.routes[chosen].Peer, chosenScore, c.network)
|
return currID
|
||||||
|
} else {
|
||||||
|
log.Infof("new chosen route is %s with peer %s with score %f for network %s", chosen, c.routes[chosen].Peer, chosenScore, c.network)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chosen
|
return chosen
|
||||||
@@ -174,7 +196,7 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
|
|||||||
return fmt.Errorf("get peer state: %v", err)
|
return fmt.Errorf("get peer state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(state.Routes, c.network.String())
|
state.DeleteRoute(c.network.String())
|
||||||
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
|
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
|
||||||
log.Warnf("Failed to update peer state: %v", err)
|
log.Warnf("Failed to update peer state: %v", err)
|
||||||
}
|
}
|
||||||
@@ -193,7 +215,7 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
|
|||||||
|
|
||||||
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
||||||
if c.chosenRoute != nil {
|
if c.chosenRoute != nil {
|
||||||
if err := removeFromRouteTableIfNonSystem(c.network, c.wgInterface.Address().IP.String(), c.wgInterface.Name()); err != nil {
|
if err := removeVPNRoute(c.network, c.wgInterface.Name()); err != nil {
|
||||||
return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
|
return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +256,7 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise add the route to the system
|
// otherwise add the route to the system
|
||||||
if err := addToRouteTableIfNoExists(c.network, c.wgInterface.Address().IP.String(), c.wgInterface.Name()); err != nil {
|
if err := addVPNRoute(c.network, c.wgInterface.Name()); err != nil {
|
||||||
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
||||||
c.network.String(), c.wgInterface.Address().IP.String(), err)
|
c.network.String(), c.wgInterface.Address().IP.String(), err)
|
||||||
}
|
}
|
||||||
@@ -246,10 +268,7 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get peer state: %v", err)
|
log.Errorf("Failed to get peer state: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if state.Routes == nil {
|
state.AddRoute(c.network.String())
|
||||||
state.Routes = map[string]struct{}{}
|
|
||||||
}
|
|
||||||
state.Routes[c.network.String()] = struct{}{}
|
|
||||||
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
|
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
|
||||||
log.Warnf("Failed to update peer state: %v", err)
|
log.Warnf("Failed to update peer state: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package routemanager
|
|||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
@@ -13,7 +14,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
statuses map[string]routerPeerStatus
|
statuses map[string]routerPeerStatus
|
||||||
expectedRouteID string
|
expectedRouteID string
|
||||||
currentRoute *route.Route
|
currentRoute string
|
||||||
existingRoutes map[string]*route.Route
|
existingRoutes map[string]*route.Route
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -32,7 +33,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer1",
|
Peer: "peer1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -51,7 +52,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer1",
|
Peer: "peer1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,7 +71,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer1",
|
Peer: "peer1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,7 +90,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer1",
|
Peer: "peer1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "",
|
expectedRouteID: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -118,7 +119,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer2",
|
Peer: "peer2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -147,7 +148,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer2",
|
Peer: "peer2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -176,18 +177,141 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
Peer: "peer2",
|
Peer: "peer2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentRoute: nil,
|
currentRoute: "",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "multiple connected peers with different latencies",
|
||||||
|
statuses: map[string]routerPeerStatus{
|
||||||
|
"route1": {
|
||||||
|
connected: true,
|
||||||
|
latency: 300 * time.Millisecond,
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
connected: true,
|
||||||
|
latency: 10 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
existingRoutes: map[string]*route.Route{
|
||||||
|
"route1": {
|
||||||
|
ID: "route1",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer1",
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
ID: "route2",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentRoute: "",
|
||||||
|
expectedRouteID: "route2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should ignore routes with latency 0",
|
||||||
|
statuses: map[string]routerPeerStatus{
|
||||||
|
"route1": {
|
||||||
|
connected: true,
|
||||||
|
latency: 0 * time.Millisecond,
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
connected: true,
|
||||||
|
latency: 10 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
existingRoutes: map[string]*route.Route{
|
||||||
|
"route1": {
|
||||||
|
ID: "route1",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer1",
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
ID: "route2",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentRoute: "",
|
||||||
|
expectedRouteID: "route2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "current route with similar score and similar but slightly worse latency should not change",
|
||||||
|
statuses: map[string]routerPeerStatus{
|
||||||
|
"route1": {
|
||||||
|
connected: true,
|
||||||
|
relayed: false,
|
||||||
|
direct: true,
|
||||||
|
latency: 12 * time.Millisecond,
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
connected: true,
|
||||||
|
relayed: false,
|
||||||
|
direct: true,
|
||||||
|
latency: 10 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
existingRoutes: map[string]*route.Route{
|
||||||
|
"route1": {
|
||||||
|
ID: "route1",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer1",
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
ID: "route2",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentRoute: "route1",
|
||||||
|
expectedRouteID: "route1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "current chosen route doesn't exist anymore",
|
||||||
|
statuses: map[string]routerPeerStatus{
|
||||||
|
"route1": {
|
||||||
|
connected: true,
|
||||||
|
relayed: false,
|
||||||
|
direct: true,
|
||||||
|
latency: 20 * time.Millisecond,
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
connected: true,
|
||||||
|
relayed: false,
|
||||||
|
direct: true,
|
||||||
|
latency: 10 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
existingRoutes: map[string]*route.Route{
|
||||||
|
"route1": {
|
||||||
|
ID: "route1",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer1",
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
ID: "route2",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentRoute: "routeDoesntExistAnymore",
|
||||||
|
expectedRouteID: "route2",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
currentRoute := &route.Route{
|
||||||
|
ID: "routeDoesntExistAnymore",
|
||||||
|
}
|
||||||
|
if tc.currentRoute != "" {
|
||||||
|
currentRoute = tc.existingRoutes[tc.currentRoute]
|
||||||
|
}
|
||||||
|
|
||||||
// create new clientNetwork
|
// create new clientNetwork
|
||||||
client := &clientNetwork{
|
client := &clientNetwork{
|
||||||
network: netip.MustParsePrefix("192.168.0.0/24"),
|
network: netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
routes: tc.existingRoutes,
|
routes: tc.existingRoutes,
|
||||||
chosenRoute: tc.currentRoute,
|
chosenRoute: currentRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenRoute := client.getBestRouteFromStatuses(tc.statuses)
|
chosenRoute := client.getBestRouteFromStatuses(tc.statuses)
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package routemanager
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +27,7 @@ var defaultv6 = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
|||||||
|
|
||||||
// Manager is a route manager interface
|
// Manager is a route manager interface
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
Init() error
|
Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error)
|
||||||
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error
|
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error
|
||||||
SetRouteChangeListener(listener listener.NetworkChangeListener)
|
SetRouteChangeListener(listener listener.NetworkChangeListener)
|
||||||
InitialRouteRange() []string
|
InitialRouteRange() []string
|
||||||
@@ -65,16 +68,25 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init sets up the routing
|
// Init sets up the routing
|
||||||
func (m *DefaultManager) Init() error {
|
func (m *DefaultManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
if nbnet.CustomRoutingDisabled() {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := cleanupRouting(); err != nil {
|
if err := cleanupRouting(); err != nil {
|
||||||
log.Warnf("Failed cleaning up routing: %v", err)
|
log.Warnf("Failed cleaning up routing: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setupRouting(); err != nil {
|
mgmtAddress := m.statusRecorder.GetManagementState().URL
|
||||||
return fmt.Errorf("setup routing: %w", err)
|
signalAddress := m.statusRecorder.GetSignalState().URL
|
||||||
|
ips := resolveURLsToIPs([]string{mgmtAddress, signalAddress})
|
||||||
|
|
||||||
|
beforePeerHook, afterPeerHook, err := setupRouting(ips, m.wgInterface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("setup routing: %w", err)
|
||||||
}
|
}
|
||||||
log.Info("Routing setup complete")
|
log.Info("Routing setup complete")
|
||||||
return nil
|
return beforePeerHook, afterPeerHook, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error {
|
func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error {
|
||||||
@@ -92,11 +104,15 @@ func (m *DefaultManager) Stop() {
|
|||||||
if m.serverRouter != nil {
|
if m.serverRouter != nil {
|
||||||
m.serverRouter.cleanUp()
|
m.serverRouter.cleanUp()
|
||||||
}
|
}
|
||||||
if err := cleanupRouting(); err != nil {
|
|
||||||
log.Errorf("Error cleaning up routing: %v", err)
|
if !nbnet.CustomRoutingDisabled() {
|
||||||
} else {
|
if err := cleanupRouting(); err != nil {
|
||||||
log.Info("Routing cleanup complete")
|
log.Errorf("Error cleaning up routing: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Routing cleanup complete")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m.ctx = nil
|
m.ctx = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,16 +219,38 @@ func (m *DefaultManager) clientRoutes(initialRoutes []*route.Route) []*route.Rou
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isPrefixSupported(prefix netip.Prefix) bool {
|
func isPrefixSupported(prefix netip.Prefix) bool {
|
||||||
if runtime.GOOS == "linux" {
|
if !nbnet.CustomRoutingDisabled() {
|
||||||
return true
|
switch runtime.GOOS {
|
||||||
|
case "linux", "windows", "darwin":
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If prefix is too small, lets assume it is a possible default prefix which is not yet supported
|
// If prefix is too small, lets assume it is a possible default prefix which is not yet supported
|
||||||
// we skip this prefix management
|
// we skip this prefix management
|
||||||
if prefix.Bits() < minRangeBits {
|
if prefix.Bits() <= minRangeBits {
|
||||||
log.Warnf("This agent version: %s, doesn't support default routes, received %s, skipping this prefix",
|
log.Warnf("This agent version: %s, doesn't support default routes, received %s, skipping this prefix",
|
||||||
version.NetbirdVersion(), prefix)
|
version.NetbirdVersion(), prefix)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveURLsToIPs takes a slice of URLs, resolves them to IP addresses and returns a slice of IPs.
|
||||||
|
func resolveURLsToIPs(urls []string) []net.IP {
|
||||||
|
var ips []net.IP
|
||||||
|
for _, rawurl := range urls {
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to parse url %s: %v", rawurl, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ipAddrs, err := net.LookupIP(u.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to resolve host %s: %v", u.Hostname(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips = append(ips, ipAddrs...)
|
||||||
|
}
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ const remotePeerKey2 = "remote1"
|
|||||||
|
|
||||||
func TestManagerUpdateRoutes(t *testing.T) {
|
func TestManagerUpdateRoutes(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
inputInitRoutes []*route.Route
|
inputInitRoutes []*route.Route
|
||||||
inputRoutes []*route.Route
|
inputRoutes []*route.Route
|
||||||
inputSerial uint64
|
inputSerial uint64
|
||||||
removeSrvRouter bool
|
removeSrvRouter bool
|
||||||
serverRoutesExpected int
|
serverRoutesExpected int
|
||||||
clientNetworkWatchersExpected int
|
clientNetworkWatchersExpected int
|
||||||
clientNetworkWatchersExpectedLinux int
|
clientNetworkWatchersExpectedAllowed int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Should create 2 client networks",
|
name: "Should create 2 client networks",
|
||||||
@@ -201,9 +201,9 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
clientNetworkWatchersExpectedLinux: 1,
|
clientNetworkWatchersExpectedAllowed: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Remove 1 Client Route",
|
name: "Remove 1 Client Route",
|
||||||
@@ -417,7 +417,9 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
statusRecorder := peer.NewRecorder("https://mgm")
|
statusRecorder := peer.NewRecorder("https://mgm")
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
routeManager := NewManager(ctx, localPeerKey, wgInterface, statusRecorder, nil)
|
routeManager := NewManager(ctx, localPeerKey, wgInterface, statusRecorder, nil)
|
||||||
err = routeManager.Init()
|
|
||||||
|
_, _, err = routeManager.Init()
|
||||||
|
|
||||||
require.NoError(t, err, "should init route manager")
|
require.NoError(t, err, "should init route manager")
|
||||||
defer routeManager.Stop()
|
defer routeManager.Stop()
|
||||||
|
|
||||||
@@ -434,8 +436,8 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
require.NoError(t, err, "should update routes")
|
require.NoError(t, err, "should update routes")
|
||||||
|
|
||||||
expectedWatchers := testCase.clientNetworkWatchersExpected
|
expectedWatchers := testCase.clientNetworkWatchersExpected
|
||||||
if runtime.GOOS == "linux" && testCase.clientNetworkWatchersExpectedLinux != 0 {
|
if (runtime.GOOS == "linux" || runtime.GOOS == "windows" || runtime.GOOS == "darwin") && testCase.clientNetworkWatchersExpectedAllowed != 0 {
|
||||||
expectedWatchers = testCase.clientNetworkWatchersExpectedLinux
|
expectedWatchers = testCase.clientNetworkWatchersExpectedAllowed
|
||||||
}
|
}
|
||||||
require.Len(t, routeManager.clientNetworks, expectedWatchers, "client networks size should match")
|
require.Len(t, routeManager.clientNetworks, expectedWatchers, "client networks size should match")
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
@@ -16,8 +17,8 @@ type MockManager struct {
|
|||||||
StopFunc func()
|
StopFunc func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockManager) Init() error {
|
func (m *MockManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
return nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitialRouteRange mock implementation of InitialRouteRange from Manager interface
|
// InitialRouteRange mock implementation of InitialRouteRange from Manager interface
|
||||||
|
|||||||
126
client/internal/routemanager/routemanager.go
Normal file
126
client/internal/routemanager/routemanager.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
//go:build !android && !ios
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ref struct {
|
||||||
|
count int
|
||||||
|
nexthop netip.Addr
|
||||||
|
intf string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteManager struct {
|
||||||
|
// refCountMap keeps track of the reference ref for prefixes
|
||||||
|
refCountMap map[netip.Prefix]ref
|
||||||
|
// prefixMap keeps track of the prefixes associated with a connection ID for removal
|
||||||
|
prefixMap map[nbnet.ConnectionID][]netip.Prefix
|
||||||
|
addRoute AddRouteFunc
|
||||||
|
removeRoute RemoveRouteFunc
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddRouteFunc func(prefix netip.Prefix) (nexthop netip.Addr, intf string, err error)
|
||||||
|
type RemoveRouteFunc func(prefix netip.Prefix, nexthop netip.Addr, intf string) error
|
||||||
|
|
||||||
|
func NewRouteManager(addRoute AddRouteFunc, removeRoute RemoveRouteFunc) *RouteManager {
|
||||||
|
// TODO: read initial routing table into refCountMap
|
||||||
|
return &RouteManager{
|
||||||
|
refCountMap: map[netip.Prefix]ref{},
|
||||||
|
prefixMap: map[nbnet.ConnectionID][]netip.Prefix{},
|
||||||
|
addRoute: addRoute,
|
||||||
|
removeRoute: removeRoute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RouteManager) AddRouteRef(connID nbnet.ConnectionID, prefix netip.Prefix) error {
|
||||||
|
rm.mutex.Lock()
|
||||||
|
defer rm.mutex.Unlock()
|
||||||
|
|
||||||
|
ref := rm.refCountMap[prefix]
|
||||||
|
log.Debugf("Increasing route ref count %d for prefix %s", ref.count, prefix)
|
||||||
|
|
||||||
|
// Add route to the system, only if it's a new prefix
|
||||||
|
if ref.count == 0 {
|
||||||
|
log.Debugf("Adding route for prefix %s", prefix)
|
||||||
|
nexthop, intf, err := rm.addRoute(prefix)
|
||||||
|
if errors.Is(err, ErrRouteNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, ErrRouteNotAllowed) {
|
||||||
|
log.Debugf("Adding route for prefix %s: %s", prefix, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add route for prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
ref.nexthop = nexthop
|
||||||
|
ref.intf = intf
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.count++
|
||||||
|
rm.refCountMap[prefix] = ref
|
||||||
|
rm.prefixMap[connID] = append(rm.prefixMap[connID], prefix)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RouteManager) RemoveRouteRef(connID nbnet.ConnectionID) error {
|
||||||
|
rm.mutex.Lock()
|
||||||
|
defer rm.mutex.Unlock()
|
||||||
|
|
||||||
|
prefixes, ok := rm.prefixMap[connID]
|
||||||
|
if !ok {
|
||||||
|
log.Debugf("No prefixes found for connection ID %s", connID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *multierror.Error
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
ref := rm.refCountMap[prefix]
|
||||||
|
log.Debugf("Decreasing route ref count %d for prefix %s", ref.count, prefix)
|
||||||
|
if ref.count == 1 {
|
||||||
|
log.Debugf("Removing route for prefix %s", prefix)
|
||||||
|
// TODO: don't fail if the route is not found
|
||||||
|
if err := rm.removeRoute(prefix, ref.nexthop, ref.intf); err != nil {
|
||||||
|
result = multierror.Append(result, fmt.Errorf("remove route for prefix %s: %w", prefix, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(rm.refCountMap, prefix)
|
||||||
|
} else {
|
||||||
|
ref.count--
|
||||||
|
rm.refCountMap[prefix] = ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(rm.prefixMap, connID)
|
||||||
|
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush removes all references and routes from the system
|
||||||
|
func (rm *RouteManager) Flush() error {
|
||||||
|
rm.mutex.Lock()
|
||||||
|
defer rm.mutex.Unlock()
|
||||||
|
|
||||||
|
var result *multierror.Error
|
||||||
|
for prefix := range rm.refCountMap {
|
||||||
|
log.Debugf("Removing route for prefix %s", prefix)
|
||||||
|
ref := rm.refCountMap[prefix]
|
||||||
|
if err := rm.removeRoute(prefix, ref.nexthop, ref.intf); err != nil {
|
||||||
|
result = multierror.Append(result, fmt.Errorf("remove route for prefix %s: %w", prefix, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rm.refCountMap = map[netip.Prefix]ref{}
|
||||||
|
rm.prefixMap = map[nbnet.ConnectionID][]netip.Prefix{}
|
||||||
|
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
}
|
||||||
@@ -155,11 +155,13 @@ func (m *defaultServerRouter) cleanUp() {
|
|||||||
log.Errorf("Failed to remove cleanup route: %v", err)
|
log.Errorf("Failed to remove cleanup route: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state := m.statusRecorder.GetLocalPeerState()
|
|
||||||
state.Routes = nil
|
|
||||||
m.statusRecorder.UpdateLocalPeerState(state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state := m.statusRecorder.GetLocalPeerState()
|
||||||
|
state.Routes = nil
|
||||||
|
m.statusRecorder.UpdateLocalPeerState(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeToRouterPair(source string, route *route.Route) (firewall.RouterPair, error) {
|
func routeToRouterPair(source string, route *route.Route) (firewall.RouterPair, error) {
|
||||||
parsed, err := netip.ParsePrefix(source)
|
parsed, err := netip.ParsePrefix(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
428
client/internal/routemanager/systemops.go
Normal file
428
client/internal/routemanager/systemops.go
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
//go:build !android && !ios
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/libp2p/go-netroute"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var splitDefaultv4_1 = netip.PrefixFrom(netip.IPv4Unspecified(), 1)
|
||||||
|
var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1)
|
||||||
|
var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1)
|
||||||
|
var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
|
||||||
|
|
||||||
|
var ErrRouteNotFound = errors.New("route not found")
|
||||||
|
var ErrRouteNotAllowed = errors.New("route not allowed")
|
||||||
|
|
||||||
|
// TODO: fix: for default our wg address now appears as the default gw
|
||||||
|
func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
||||||
|
addr := netip.IPv4Unspecified()
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
addr = netip.IPv6Unspecified()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultGateway, _, err := getNextHop(addr)
|
||||||
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
|
return fmt.Errorf("get existing route gateway: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !prefix.Contains(defaultGateway) {
|
||||||
|
log.Debugf("Skipping adding a new route for gateway %s because it is not in the network %s", defaultGateway, prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayPrefix := netip.PrefixFrom(defaultGateway, 32)
|
||||||
|
if defaultGateway.Is6() {
|
||||||
|
gatewayPrefix = netip.PrefixFrom(defaultGateway, 128)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := existsInRouteTable(gatewayPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to check if there is an existing route for gateway %s. error: %s", gatewayPrefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
log.Debugf("Skipping adding a new route for gateway %s because it already exists", gatewayPrefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitIntf string
|
||||||
|
gatewayHop, intf, err := getNextHop(defaultGateway)
|
||||||
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
|
return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err)
|
||||||
|
}
|
||||||
|
if intf != nil {
|
||||||
|
exitIntf = intf.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, gatewayHop)
|
||||||
|
return addToRouteTable(gatewayPrefix, gatewayHop, exitIntf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextHop(ip netip.Addr) (netip.Addr, *net.Interface, error) {
|
||||||
|
r, err := netroute.New()
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, nil, fmt.Errorf("new netroute: %w", err)
|
||||||
|
}
|
||||||
|
intf, gateway, preferredSrc, err := r.Route(ip.AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to get route for %s: %v", ip, err)
|
||||||
|
return netip.Addr{}, nil, ErrRouteNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Route for %s: interface %v, nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc)
|
||||||
|
if gateway == nil {
|
||||||
|
if preferredSrc == nil {
|
||||||
|
return netip.Addr{}, nil, ErrRouteNotFound
|
||||||
|
}
|
||||||
|
log.Debugf("No next hop found for ip %s, using preferred source %s", ip, preferredSrc)
|
||||||
|
|
||||||
|
addr, err := ipToAddr(preferredSrc, intf)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, nil, fmt.Errorf("convert preferred source to address: %w", err)
|
||||||
|
}
|
||||||
|
return addr.Unmap(), intf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := ipToAddr(gateway, intf)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, nil, fmt.Errorf("convert gateway to address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, intf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts a net.IP to a netip.Addr including the zone based on the passed interface
|
||||||
|
func ipToAddr(ip net.IP, intf *net.Interface) (netip.Addr, error) {
|
||||||
|
addr, ok := netip.AddrFromSlice(ip)
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}, fmt.Errorf("failed to convert IP address to netip.Addr: %s", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
if intf != nil && (addr.IsLinkLocalMulticast() || addr.IsLinkLocalUnicast()) {
|
||||||
|
log.Tracef("Adding zone %s to address %s", intf.Name, addr)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
addr = addr.WithZone(strconv.Itoa(intf.Index))
|
||||||
|
} else {
|
||||||
|
addr = addr.WithZone(intf.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr.Unmap(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
||||||
|
routes, err := getRoutesFromTable()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("get routes from table: %w", err)
|
||||||
|
}
|
||||||
|
for _, tableRoute := range routes {
|
||||||
|
if tableRoute == prefix {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubRange(prefix netip.Prefix) (bool, error) {
|
||||||
|
routes, err := getRoutesFromTable()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("get routes from table: %w", err)
|
||||||
|
}
|
||||||
|
for _, tableRoute := range routes {
|
||||||
|
if tableRoute.Bits() > minRangeBits && tableRoute.Contains(prefix.Addr()) && tableRoute.Bits() < prefix.Bits() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
||||||
|
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
||||||
|
func addRouteToNonVPNIntf(
|
||||||
|
prefix netip.Prefix,
|
||||||
|
vpnIntf *iface.WGIface,
|
||||||
|
initialNextHop netip.Addr,
|
||||||
|
initialIntf *net.Interface,
|
||||||
|
) (netip.Addr, string, error) {
|
||||||
|
addr := prefix.Addr()
|
||||||
|
switch {
|
||||||
|
case addr.IsLoopback(),
|
||||||
|
addr.IsLinkLocalUnicast(),
|
||||||
|
addr.IsLinkLocalMulticast(),
|
||||||
|
addr.IsInterfaceLocalMulticast(),
|
||||||
|
addr.IsUnspecified(),
|
||||||
|
addr.IsMulticast():
|
||||||
|
|
||||||
|
return netip.Addr{}, "", ErrRouteNotAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the exit interface and next hop for the prefix, so we can add a specific route
|
||||||
|
nexthop, intf, err := getNextHop(addr)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, "", fmt.Errorf("get next hop: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop, prefix, intf)
|
||||||
|
exitNextHop := nexthop
|
||||||
|
var exitIntf string
|
||||||
|
if intf != nil {
|
||||||
|
exitIntf = intf.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
vpnAddr, ok := netip.AddrFromSlice(vpnIntf.Address().IP)
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}, "", fmt.Errorf("failed to convert vpn address to netip.Addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if next hop is the VPN address or the interface is the VPN interface, we should use the initial values
|
||||||
|
if exitNextHop == vpnAddr || exitIntf == vpnIntf.Name() {
|
||||||
|
log.Debugf("Route for prefix %s is pointing to the VPN interface", prefix)
|
||||||
|
exitNextHop = initialNextHop
|
||||||
|
if initialIntf != nil {
|
||||||
|
exitIntf = initialIntf.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Adding a new route for prefix %s with next hop %s", prefix, exitNextHop)
|
||||||
|
if err := addToRouteTable(prefix, exitNextHop, exitIntf); err != nil {
|
||||||
|
return netip.Addr{}, "", fmt.Errorf("add route to table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitNextHop, exitIntf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix
|
||||||
|
// in two /1 prefixes to avoid replacing the existing default route
|
||||||
|
func genericAddVPNRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
if prefix == defaultv4 {
|
||||||
|
if err := addToRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := addToRouteTable(splitDefaultv4_2, netip.Addr{}, intf); err != nil {
|
||||||
|
if err2 := removeFromRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err2 != nil {
|
||||||
|
log.Warnf("Failed to rollback route addition: %s", err2)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove once IPv6 is supported on the interface
|
||||||
|
if err := addToRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
||||||
|
return fmt.Errorf("add unreachable route split 1: %w", err)
|
||||||
|
}
|
||||||
|
if err := addToRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
||||||
|
if err2 := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err2 != nil {
|
||||||
|
log.Warnf("Failed to rollback route addition: %s", err2)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("add unreachable route split 2: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else if prefix == defaultv6 {
|
||||||
|
if err := addToRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
||||||
|
return fmt.Errorf("add unreachable route split 1: %w", err)
|
||||||
|
}
|
||||||
|
if err := addToRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
||||||
|
if err2 := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err2 != nil {
|
||||||
|
log.Warnf("Failed to rollback route addition: %s", err2)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("add unreachable route split 2: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return addNonExistingRoute(prefix, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table
|
||||||
|
func addNonExistingRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
ok, err := existsInRouteTable(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("exists in route table: %w", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
log.Warnf("Skipping adding a new route for network %s because it already exists", prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = isSubRange(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sub range: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
err := addRouteForCurrentDefaultGateway(prefix)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to add route for current default gateway route. Will proceed without it. error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addToRouteTable(prefix, netip.Addr{}, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given,
|
||||||
|
// it will remove the split /1 prefixes
|
||||||
|
func genericRemoveVPNRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
if prefix == defaultv4 {
|
||||||
|
var result *multierror.Error
|
||||||
|
if err := removeFromRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
if err := removeFromRouteTable(splitDefaultv4_2, netip.Addr{}, intf); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove once IPv6 is supported on the interface
|
||||||
|
if err := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
if err := removeFromRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
} else if prefix == defaultv6 {
|
||||||
|
var result *multierror.Error
|
||||||
|
if err := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
if err := removeFromRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeFromRouteTable(prefix, netip.Addr{}, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrefixFromIP(ip net.IP) (*netip.Prefix, error) {
|
||||||
|
addr, ok := netip.AddrFromSlice(ip)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("parse IP address: %s", ip)
|
||||||
|
}
|
||||||
|
addr = addr.Unmap()
|
||||||
|
|
||||||
|
var prefixLength int
|
||||||
|
switch {
|
||||||
|
case addr.Is4():
|
||||||
|
prefixLength = 32
|
||||||
|
case addr.Is6():
|
||||||
|
prefixLength = 128
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid IP address: %s", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := netip.PrefixFrom(addr, prefixLength)
|
||||||
|
return &prefix, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRoutingWithRouteManager(routeManager **RouteManager, initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
initialNextHopV4, initialIntfV4, err := getNextHop(netip.IPv4Unspecified())
|
||||||
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
|
log.Errorf("Unable to get initial v4 default next hop: %v", err)
|
||||||
|
}
|
||||||
|
initialNextHopV6, initialIntfV6, err := getNextHop(netip.IPv6Unspecified())
|
||||||
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
|
log.Errorf("Unable to get initial v6 default next hop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*routeManager = NewRouteManager(
|
||||||
|
func(prefix netip.Prefix) (netip.Addr, string, error) {
|
||||||
|
addr := prefix.Addr()
|
||||||
|
nexthop, intf := initialNextHopV4, initialIntfV4
|
||||||
|
if addr.Is6() {
|
||||||
|
nexthop, intf = initialNextHopV6, initialIntfV6
|
||||||
|
}
|
||||||
|
return addRouteToNonVPNIntf(prefix, wgIface, nexthop, intf)
|
||||||
|
},
|
||||||
|
removeFromRouteTable,
|
||||||
|
)
|
||||||
|
|
||||||
|
return setupHooks(*routeManager, initAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupRoutingWithRouteManager(routeManager *RouteManager) error {
|
||||||
|
if routeManager == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove hooks selectively
|
||||||
|
nbnet.RemoveDialerHooks()
|
||||||
|
nbnet.RemoveListenerHooks()
|
||||||
|
|
||||||
|
if err := routeManager.Flush(); err != nil {
|
||||||
|
return fmt.Errorf("flush route manager: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupHooks(routeManager *RouteManager, initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
beforeHook := func(connID nbnet.ConnectionID, ip net.IP) error {
|
||||||
|
prefix, err := getPrefixFromIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convert ip to prefix: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := routeManager.AddRouteRef(connID, *prefix); err != nil {
|
||||||
|
return fmt.Errorf("adding route reference: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
afterHook := func(connID nbnet.ConnectionID) error {
|
||||||
|
if err := routeManager.RemoveRouteRef(connID); err != nil {
|
||||||
|
return fmt.Errorf("remove route reference: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range initAddresses {
|
||||||
|
if err := beforeHook("init", ip); err != nil {
|
||||||
|
log.Errorf("Failed to add route reference: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nbnet.AddDialerHook(func(ctx context.Context, connID nbnet.ConnectionID, resolvedIPs []net.IPAddr) error {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *multierror.Error
|
||||||
|
for _, ip := range resolvedIPs {
|
||||||
|
result = multierror.Append(result, beforeHook(connID, ip.IP))
|
||||||
|
}
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
})
|
||||||
|
|
||||||
|
nbnet.AddDialerCloseHook(func(connID nbnet.ConnectionID, conn *net.Conn) error {
|
||||||
|
return afterHook(connID)
|
||||||
|
})
|
||||||
|
|
||||||
|
nbnet.AddListenerWriteHook(func(connID nbnet.ConnectionID, ip *net.IPAddr, data []byte) error {
|
||||||
|
return beforeHook(connID, ip.IP)
|
||||||
|
})
|
||||||
|
|
||||||
|
nbnet.AddListenerCloseHook(func(connID nbnet.ConnectionID, conn net.PacketConn) error {
|
||||||
|
return afterHook(connID)
|
||||||
|
})
|
||||||
|
|
||||||
|
return beforeHook, afterHook, nil
|
||||||
|
}
|
||||||
@@ -1,13 +1,33 @@
|
|||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addToRouteTableIfNoExists(prefix netip.Prefix, addr, intf string) error {
|
func setupRouting([]net.IP, *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupRouting() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr, intf string) error {
|
func enableIPForwarding() error {
|
||||||
|
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVPNRoute(netip.Prefix, string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVPNRoute(netip.Prefix, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,16 +52,24 @@ func getRoutesFromTable() ([]netip.Prefix, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(m.Addrs) < 3 {
|
||||||
|
log.Warnf("Unexpected RIB message Addrs: %v", m.Addrs)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
addr, ok := toNetIPAddr(m.Addrs[0])
|
addr, ok := toNetIPAddr(m.Addrs[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mask, ok := toNetIPMASK(m.Addrs[2])
|
cidr := 32
|
||||||
if !ok {
|
if mask := m.Addrs[2]; mask != nil {
|
||||||
continue
|
cidr, ok = toCIDR(mask)
|
||||||
|
if !ok {
|
||||||
|
log.Debugf("Unexpected RIB message Addrs[2]: %v", mask)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cidr, _ := mask.Size()
|
|
||||||
|
|
||||||
routePrefix := netip.PrefixFrom(addr, cidr)
|
routePrefix := netip.PrefixFrom(addr, cidr)
|
||||||
if routePrefix.IsValid() {
|
if routePrefix.IsValid() {
|
||||||
@@ -73,20 +82,19 @@ func getRoutesFromTable() ([]netip.Prefix, error) {
|
|||||||
func toNetIPAddr(a route.Addr) (netip.Addr, bool) {
|
func toNetIPAddr(a route.Addr) (netip.Addr, bool) {
|
||||||
switch t := a.(type) {
|
switch t := a.(type) {
|
||||||
case *route.Inet4Addr:
|
case *route.Inet4Addr:
|
||||||
ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
|
return netip.AddrFrom4(t.IP), true
|
||||||
addr := netip.MustParseAddr(ip.String())
|
|
||||||
return addr, true
|
|
||||||
default:
|
default:
|
||||||
return netip.Addr{}, false
|
return netip.Addr{}, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNetIPMASK(a route.Addr) (net.IPMask, bool) {
|
func toCIDR(a route.Addr) (int, bool) {
|
||||||
switch t := a.(type) {
|
switch t := a.(type) {
|
||||||
case *route.Inet4Addr:
|
case *route.Inet4Addr:
|
||||||
mask := net.IPv4Mask(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
|
mask := net.IPv4Mask(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
|
||||||
return mask, true
|
cidr, _ := mask.Size()
|
||||||
|
return cidr, true
|
||||||
default:
|
default:
|
||||||
return nil, false
|
return 0, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && !ios
|
|
||||||
|
|
||||||
package routemanager
|
|
||||||
|
|
||||||
import "net/netip"
|
|
||||||
|
|
||||||
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, intf string) error {
|
|
||||||
return genericAddToRouteTableIfNoExists(prefix, addr, intf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string, intf string) error {
|
|
||||||
return genericRemoveFromRouteTableIfNonSystem(prefix, addr, intf)
|
|
||||||
}
|
|
||||||
89
client/internal/routemanager/systemops_darwin.go
Normal file
89
client/internal/routemanager/systemops_darwin.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//go:build darwin && !ios
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
)
|
||||||
|
|
||||||
|
var routeManager *RouteManager
|
||||||
|
|
||||||
|
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupRouting() error {
|
||||||
|
return cleanupRoutingWithRouteManager(routeManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
||||||
|
return routeCmd("add", prefix, nexthop, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
||||||
|
return routeCmd("delete", prefix, nexthop, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func routeCmd(action string, prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
||||||
|
inet := "-inet"
|
||||||
|
network := prefix.String()
|
||||||
|
if prefix.IsSingleIP() {
|
||||||
|
network = prefix.Addr().String()
|
||||||
|
}
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
inet = "-inet6"
|
||||||
|
// Special case for IPv6 split default route, pointing to the wg interface fails
|
||||||
|
// TODO: Remove once we have IPv6 support on the interface
|
||||||
|
if prefix.Bits() == 1 {
|
||||||
|
intf = "lo0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"-n", action, inet, network}
|
||||||
|
if nexthop.IsValid() {
|
||||||
|
args = append(args, nexthop.Unmap().String())
|
||||||
|
} else if intf != "" {
|
||||||
|
args = append(args, "-interface", intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := retryRouteCmd(args); err != nil {
|
||||||
|
return fmt.Errorf("failed to %s route for %s: %w", action, prefix, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func retryRouteCmd(args []string) error {
|
||||||
|
operation := func() error {
|
||||||
|
out, err := exec.Command("route", args...).CombinedOutput()
|
||||||
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
||||||
|
// https://github.com/golang/go/issues/45736
|
||||||
|
if err != nil && strings.Contains(string(out), "sysctl: cannot allocate memory") {
|
||||||
|
return err
|
||||||
|
} else if err != nil {
|
||||||
|
return backoff.Permanent(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expBackOff := backoff.NewExponentialBackOff()
|
||||||
|
expBackOff.InitialInterval = 50 * time.Millisecond
|
||||||
|
expBackOff.MaxInterval = 500 * time.Millisecond
|
||||||
|
expBackOff.MaxElapsedTime = 1 * time.Second
|
||||||
|
|
||||||
|
err := backoff.Retry(operation, expBackOff)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("route cmd retry failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
138
client/internal/routemanager/systemops_darwin_test.go
Normal file
138
client/internal/routemanager/systemops_darwin_test.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
//go:build !ios
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var expectedVPNint = "utun100"
|
||||||
|
var expectedExternalInt = "lo0"
|
||||||
|
var expectedInternalInt = "lo0"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testCases = append(testCases, []testCase{
|
||||||
|
{
|
||||||
|
name: "To more specific route without custom dialer via vpn",
|
||||||
|
destination: "10.10.0.2:53",
|
||||||
|
expectedInterface: expectedVPNint,
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
expectedPacket: createPacketExpectation("100.64.0.1", 12345, "10.10.0.2", 53),
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentRoutes(t *testing.T) {
|
||||||
|
baseIP := netip.MustParseAddr("192.0.2.0")
|
||||||
|
intf := "lo0"
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(ip netip.Addr) {
|
||||||
|
defer wg.Done()
|
||||||
|
prefix := netip.PrefixFrom(ip, 32)
|
||||||
|
if err := addToRouteTable(prefix, netip.Addr{}, intf); err != nil {
|
||||||
|
t.Errorf("Failed to add route for %s: %v", prefix, err)
|
||||||
|
}
|
||||||
|
}(baseIP)
|
||||||
|
baseIP = baseIP.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
baseIP = netip.MustParseAddr("192.0.2.0")
|
||||||
|
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(ip netip.Addr) {
|
||||||
|
defer wg.Done()
|
||||||
|
prefix := netip.PrefixFrom(ip, 32)
|
||||||
|
if err := removeFromRouteTable(prefix, netip.Addr{}, intf); err != nil {
|
||||||
|
t.Errorf("Failed to remove route for %s: %v", prefix, err)
|
||||||
|
}
|
||||||
|
}(baseIP)
|
||||||
|
baseIP = baseIP.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAndSetupDummyInterface(t *testing.T, intf string, ipAddressCIDR string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
err := exec.Command("ifconfig", intf, "alias", ipAddressCIDR).Run()
|
||||||
|
require.NoError(t, err, "Failed to create loopback alias")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := exec.Command("ifconfig", intf, ipAddressCIDR, "-alias").Run()
|
||||||
|
assert.NoError(t, err, "Failed to remove loopback alias")
|
||||||
|
})
|
||||||
|
|
||||||
|
return "lo0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDummyRoute(t *testing.T, dstCIDR string, gw net.IP, _ string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var originalNexthop net.IP
|
||||||
|
if dstCIDR == "0.0.0.0/0" {
|
||||||
|
var err error
|
||||||
|
originalNexthop, err = fetchOriginalGateway()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Failed to fetch original gateway: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if output, err := exec.Command("route", "delete", "-net", dstCIDR).CombinedOutput(); err != nil {
|
||||||
|
t.Logf("Failed to delete route: %v, output: %s", err, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if originalNexthop != nil {
|
||||||
|
err := exec.Command("route", "add", "-net", dstCIDR, originalNexthop.String()).Run()
|
||||||
|
assert.NoError(t, err, "Failed to restore original route")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
err := exec.Command("route", "add", "-net", dstCIDR, gw.String()).Run()
|
||||||
|
require.NoError(t, err, "Failed to add route")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := exec.Command("route", "delete", "-net", dstCIDR).Run()
|
||||||
|
assert.NoError(t, err, "Failed to remove route")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchOriginalGateway() (net.IP, error) {
|
||||||
|
output, err := exec.Command("route", "-n", "get", "default").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := regexp.MustCompile(`gateway: (\S+)`).FindStringSubmatch(string(output))
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil, fmt.Errorf("gateway not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ParseIP(matches[1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDummyInterfacesAndRoutes(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
defaultDummy := createAndSetupDummyInterface(t, expectedExternalInt, "192.168.0.1/24")
|
||||||
|
addDummyRoute(t, "0.0.0.0/0", net.IPv4(192, 168, 0, 1), defaultDummy)
|
||||||
|
|
||||||
|
otherDummy := createAndSetupDummyInterface(t, expectedInternalInt, "192.168.1.1/24")
|
||||||
|
addDummyRoute(t, "10.0.0.0/8", net.IPv4(192, 168, 1, 1), otherDummy)
|
||||||
|
}
|
||||||
@@ -1,13 +1,33 @@
|
|||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addToRouteTableIfNoExists(prefix netip.Prefix, addr, intf string) error {
|
func setupRouting([]net.IP, *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupRouting() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr, intf string) error {
|
func enableIPForwarding() error {
|
||||||
|
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVPNRoute(netip.Prefix, string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVPNRoute(netip.Prefix, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,16 +32,31 @@ const (
|
|||||||
rtTablesPath = "/etc/iproute2/rt_tables"
|
rtTablesPath = "/etc/iproute2/rt_tables"
|
||||||
|
|
||||||
// ipv4ForwardingPath is the path to the file containing the IP forwarding setting.
|
// ipv4ForwardingPath is the path to the file containing the IP forwarding setting.
|
||||||
ipv4ForwardingPath = "/proc/sys/net/ipv4/ip_forward"
|
ipv4ForwardingPath = "net.ipv4.ip_forward"
|
||||||
|
|
||||||
|
rpFilterPath = "net.ipv4.conf.all.rp_filter"
|
||||||
|
rpFilterInterfacePath = "net.ipv4.conf.%s.rp_filter"
|
||||||
|
srcValidMarkPath = "net.ipv4.conf.all.src_valid_mark"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrTableIDExists = errors.New("ID exists with different name")
|
var ErrTableIDExists = errors.New("ID exists with different name")
|
||||||
|
|
||||||
|
var routeManager = &RouteManager{}
|
||||||
|
|
||||||
|
// originalSysctl stores the original sysctl values before they are modified
|
||||||
|
var originalSysctl map[string]int
|
||||||
|
|
||||||
|
// determines whether to use the legacy routing setup
|
||||||
|
var isLegacy = os.Getenv("NB_USE_LEGACY_ROUTING") == "true" || nbnet.CustomRoutingDisabled()
|
||||||
|
|
||||||
|
// sysctlFailed is used as an indicator to emit a warning when default routes are configured
|
||||||
|
var sysctlFailed bool
|
||||||
|
|
||||||
type ruleParams struct {
|
type ruleParams struct {
|
||||||
|
priority int
|
||||||
fwmark int
|
fwmark int
|
||||||
tableID int
|
tableID int
|
||||||
family int
|
family int
|
||||||
priority int
|
|
||||||
invert bool
|
invert bool
|
||||||
suppressPrefix int
|
suppressPrefix int
|
||||||
description string
|
description string
|
||||||
@@ -45,10 +64,10 @@ type ruleParams struct {
|
|||||||
|
|
||||||
func getSetupRules() []ruleParams {
|
func getSetupRules() []ruleParams {
|
||||||
return []ruleParams{
|
return []ruleParams{
|
||||||
{nbnet.NetbirdFwmark, NetbirdVPNTableID, netlink.FAMILY_V4, -1, true, -1, "add rule v4 netbird"},
|
{100, -1, syscall.RT_TABLE_MAIN, netlink.FAMILY_V4, false, 0, "rule with suppress prefixlen v4"},
|
||||||
{nbnet.NetbirdFwmark, NetbirdVPNTableID, netlink.FAMILY_V6, -1, true, -1, "add rule v6 netbird"},
|
{100, -1, syscall.RT_TABLE_MAIN, netlink.FAMILY_V6, false, 0, "rule with suppress prefixlen v6"},
|
||||||
{-1, syscall.RT_TABLE_MAIN, netlink.FAMILY_V4, -1, false, 0, "add rule with suppress prefixlen v4"},
|
{110, nbnet.NetbirdFwmark, NetbirdVPNTableID, netlink.FAMILY_V4, true, -1, "rule v4 netbird"},
|
||||||
{-1, syscall.RT_TABLE_MAIN, netlink.FAMILY_V6, -1, false, 0, "add rule with suppress prefixlen v6"},
|
{110, nbnet.NetbirdFwmark, NetbirdVPNTableID, netlink.FAMILY_V6, true, -1, "rule v6 netbird"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,13 +81,23 @@ func getSetupRules() []ruleParams {
|
|||||||
// Rule 2 (VPN Traffic Routing): Directs all remaining traffic to the 'NetbirdVPNTableID' custom routing table.
|
// Rule 2 (VPN Traffic Routing): Directs all remaining traffic to the 'NetbirdVPNTableID' custom routing table.
|
||||||
// This table is where a default route or other specific routes received from the management server are configured,
|
// This table is where a default route or other specific routes received from the management server are configured,
|
||||||
// enabling VPN connectivity.
|
// enabling VPN connectivity.
|
||||||
//
|
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) {
|
||||||
// The rules are inserted in reverse order, as rules are added from the bottom up in the rule list.
|
if isLegacy {
|
||||||
func setupRouting() (err error) {
|
log.Infof("Using legacy routing setup")
|
||||||
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
||||||
|
}
|
||||||
|
|
||||||
if err = addRoutingTableName(); err != nil {
|
if err = addRoutingTableName(); err != nil {
|
||||||
log.Errorf("Error adding routing table name: %v", err)
|
log.Errorf("Error adding routing table name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalValues, err := setupSysctl(wgIface)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error setting up sysctl: %v", err)
|
||||||
|
sysctlFailed = true
|
||||||
|
}
|
||||||
|
originalSysctl = originalValues
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cleanErr := cleanupRouting(); cleanErr != nil {
|
if cleanErr := cleanupRouting(); cleanErr != nil {
|
||||||
@@ -80,17 +109,26 @@ func setupRouting() (err error) {
|
|||||||
rules := getSetupRules()
|
rules := getSetupRules()
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if err := addRule(rule); err != nil {
|
if err := addRule(rule); err != nil {
|
||||||
return fmt.Errorf("%s: %w", rule.description, err)
|
if errors.Is(err, syscall.EOPNOTSUPP) {
|
||||||
|
log.Warnf("Rule operations are not supported, falling back to the legacy routing setup")
|
||||||
|
isLegacy = true
|
||||||
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("%s: %w", rule.description, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanupRouting performs a thorough cleanup of the routing configuration established by 'setupRouting'.
|
// cleanupRouting performs a thorough cleanup of the routing configuration established by 'setupRouting'.
|
||||||
// It systematically removes the three rules and any associated routing table entries to ensure a clean state.
|
// It systematically removes the three rules and any associated routing table entries to ensure a clean state.
|
||||||
// The function uses error aggregation to report any errors encountered during the cleanup process.
|
// The function uses error aggregation to report any errors encountered during the cleanup process.
|
||||||
func cleanupRouting() error {
|
func cleanupRouting() error {
|
||||||
|
if isLegacy {
|
||||||
|
return cleanupRoutingWithRouteManager(routeManager)
|
||||||
|
}
|
||||||
|
|
||||||
var result *multierror.Error
|
var result *multierror.Error
|
||||||
|
|
||||||
if err := flushRoutes(NetbirdVPNTableID, netlink.FAMILY_V4); err != nil {
|
if err := flushRoutes(NetbirdVPNTableID, netlink.FAMILY_V4); err != nil {
|
||||||
@@ -102,165 +140,79 @@ func cleanupRouting() error {
|
|||||||
|
|
||||||
rules := getSetupRules()
|
rules := getSetupRules()
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if err := removeAllRules(rule); err != nil {
|
if err := removeRule(rule); err != nil {
|
||||||
result = multierror.Append(result, fmt.Errorf("%s: %w", rule.description, err))
|
result = multierror.Append(result, fmt.Errorf("%s: %w", rule.description, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := cleanupSysctl(originalSysctl); err != nil {
|
||||||
|
result = multierror.Append(result, fmt.Errorf("cleanup sysctl: %w", err))
|
||||||
|
}
|
||||||
|
originalSysctl = nil
|
||||||
|
sysctlFailed = false
|
||||||
|
|
||||||
return result.ErrorOrNil()
|
return result.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToRouteTableIfNoExists(prefix netip.Prefix, _ string, intf string) error {
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
||||||
// No need to check if routes exist as main table takes precedence over the VPN table via Rule 2
|
return addRoute(prefix, nexthop, intf, syscall.RT_TABLE_MAIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
||||||
|
return removeRoute(prefix, nexthop, intf, syscall.RT_TABLE_MAIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVPNRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
if isLegacy {
|
||||||
|
return genericAddVPNRoute(prefix, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sysctlFailed && (prefix == defaultv4 || prefix == defaultv6) {
|
||||||
|
log.Warnf("Default route is configured but sysctl operations failed, VPN traffic may not be routed correctly, consider using NB_USE_LEGACY_ROUTING=true or setting net.ipv4.conf.*.rp_filter to 2 (loose) or 0 (off)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to check if routes exist as main table takes precedence over the VPN table via Rule 1
|
||||||
|
|
||||||
// TODO remove this once we have ipv6 support
|
// TODO remove this once we have ipv6 support
|
||||||
if prefix == defaultv4 {
|
if prefix == defaultv4 {
|
||||||
if err := addUnreachableRoute(&defaultv6, NetbirdVPNTableID, netlink.FAMILY_V6); err != nil {
|
if err := addUnreachableRoute(defaultv6, NetbirdVPNTableID); err != nil {
|
||||||
return fmt.Errorf("add blackhole: %w", err)
|
return fmt.Errorf("add blackhole: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := addRoute(&prefix, nil, &intf, NetbirdVPNTableID, netlink.FAMILY_V4); err != nil {
|
if err := addRoute(prefix, netip.Addr{}, intf, NetbirdVPNTableID); err != nil {
|
||||||
return fmt.Errorf("add route: %w", err)
|
return fmt.Errorf("add route: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, _ string, intf string) error {
|
func removeVPNRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
if isLegacy {
|
||||||
|
return genericRemoveVPNRoute(prefix, intf)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO remove this once we have ipv6 support
|
// TODO remove this once we have ipv6 support
|
||||||
if prefix == defaultv4 {
|
if prefix == defaultv4 {
|
||||||
if err := removeUnreachableRoute(&defaultv6, NetbirdVPNTableID, netlink.FAMILY_V6); err != nil {
|
if err := removeUnreachableRoute(defaultv6, NetbirdVPNTableID); err != nil {
|
||||||
return fmt.Errorf("remove unreachable route: %w", err)
|
return fmt.Errorf("remove unreachable route: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := removeRoute(&prefix, nil, &intf, NetbirdVPNTableID, netlink.FAMILY_V4); err != nil {
|
if err := removeRoute(prefix, netip.Addr{}, intf, NetbirdVPNTableID); err != nil {
|
||||||
return fmt.Errorf("remove route: %w", err)
|
return fmt.Errorf("remove route: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRoutesFromTable() ([]netip.Prefix, error) {
|
func getRoutesFromTable() ([]netip.Prefix, error) {
|
||||||
return getRoutes(NetbirdVPNTableID, netlink.FAMILY_V4)
|
v4Routes, err := getRoutes(syscall.RT_TABLE_MAIN, netlink.FAMILY_V4)
|
||||||
}
|
|
||||||
|
|
||||||
// addRoute adds a route to a specific routing table identified by tableID.
|
|
||||||
func addRoute(prefix *netip.Prefix, addr, intf *string, tableID, family int) error {
|
|
||||||
route := &netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
|
||||||
Table: tableID,
|
|
||||||
Family: family,
|
|
||||||
}
|
|
||||||
|
|
||||||
if prefix != nil {
|
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
|
||||||
}
|
|
||||||
route.Dst = ipNet
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := addNextHop(addr, intf, route); err != nil {
|
|
||||||
return fmt.Errorf("add gateway and device: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) {
|
|
||||||
return fmt.Errorf("netlink add route: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addUnreachableRoute adds an unreachable route for the specified IP family and routing table.
|
|
||||||
// ipFamily should be netlink.FAMILY_V4 for IPv4 or netlink.FAMILY_V6 for IPv6.
|
|
||||||
// tableID specifies the routing table to which the unreachable route will be added.
|
|
||||||
func addUnreachableRoute(prefix *netip.Prefix, tableID, ipFamily int) error {
|
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
return nil, fmt.Errorf("get v4 routes: %w", err)
|
||||||
}
|
}
|
||||||
|
v6Routes, err := getRoutes(syscall.RT_TABLE_MAIN, netlink.FAMILY_V6)
|
||||||
route := &netlink.Route{
|
|
||||||
Type: syscall.RTN_UNREACHABLE,
|
|
||||||
Table: tableID,
|
|
||||||
Family: ipFamily,
|
|
||||||
Dst: ipNet,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) {
|
|
||||||
return fmt.Errorf("netlink add unreachable route: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeUnreachableRoute(prefix *netip.Prefix, tableID, ipFamily int) error {
|
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
return nil, fmt.Errorf("get v6 routes: %w", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return append(v4Routes, v6Routes...), nil
|
||||||
route := &netlink.Route{
|
|
||||||
Type: syscall.RTN_UNREACHABLE,
|
|
||||||
Table: tableID,
|
|
||||||
Family: ipFamily,
|
|
||||||
Dst: ipNet,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) {
|
|
||||||
return fmt.Errorf("netlink remove unreachable route: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeRoute removes a route from a specific routing table identified by tableID.
|
|
||||||
func removeRoute(prefix *netip.Prefix, addr, intf *string, tableID, family int) error {
|
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
route := &netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
|
||||||
Table: tableID,
|
|
||||||
Family: family,
|
|
||||||
Dst: ipNet,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := addNextHop(addr, intf, route); err != nil {
|
|
||||||
return fmt.Errorf("add gateway and device: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) {
|
|
||||||
return fmt.Errorf("netlink remove route: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func flushRoutes(tableID, family int) error {
|
|
||||||
routes, err := netlink.RouteListFiltered(family, &netlink.Route{Table: tableID}, netlink.RT_FILTER_TABLE)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list routes from table %d: %w", tableID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result *multierror.Error
|
|
||||||
for i := range routes {
|
|
||||||
route := routes[i]
|
|
||||||
// unreachable default routes don't come back with Dst set
|
|
||||||
if route.Gw == nil && route.Src == nil && route.Dst == nil {
|
|
||||||
if family == netlink.FAMILY_V4 {
|
|
||||||
routes[i].Dst = &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}
|
|
||||||
} else {
|
|
||||||
routes[i].Dst = &net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := netlink.RouteDel(&routes[i]); err != nil {
|
|
||||||
result = multierror.Append(result, fmt.Errorf("failed to delete route %v from table %d: %w", routes[i], tableID, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ErrorOrNil()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRoutes fetches routes from a specific routing table identified by tableID.
|
// getRoutes fetches routes from a specific routing table identified by tableID.
|
||||||
@@ -291,25 +243,130 @@ func getRoutes(tableID, family int) ([]netip.Prefix, error) {
|
|||||||
return prefixList, nil
|
return prefixList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func enableIPForwarding() error {
|
// addRoute adds a route to a specific routing table identified by tableID.
|
||||||
bytes, err := os.ReadFile(ipv4ForwardingPath)
|
func addRoute(prefix netip.Prefix, addr netip.Addr, intf string, tableID int) error {
|
||||||
|
route := &netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Table: tableID,
|
||||||
|
Family: getAddressFamily(prefix),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read file %s: %w", ipv4ForwardingPath, err)
|
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
route.Dst = ipNet
|
||||||
|
|
||||||
|
if err := addNextHop(addr, intf, route); err != nil {
|
||||||
|
return fmt.Errorf("add gateway and device: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if it is already enabled
|
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
// see more: https://github.com/netbirdio/netbird/issues/872
|
return fmt.Errorf("netlink add route: %w", err)
|
||||||
if len(bytes) > 0 && bytes[0] == 49 {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gosec
|
|
||||||
if err := os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644); err != nil {
|
|
||||||
return fmt.Errorf("write file %s: %w", ipv4ForwardingPath, err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addUnreachableRoute adds an unreachable route for the specified IP family and routing table.
|
||||||
|
// ipFamily should be netlink.FAMILY_V4 for IPv4 or netlink.FAMILY_V6 for IPv6.
|
||||||
|
// tableID specifies the routing table to which the unreachable route will be added.
|
||||||
|
func addUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
||||||
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
route := &netlink.Route{
|
||||||
|
Type: syscall.RTN_UNREACHABLE,
|
||||||
|
Table: tableID,
|
||||||
|
Family: getAddressFamily(prefix),
|
||||||
|
Dst: ipNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
return fmt.Errorf("netlink add unreachable route: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
||||||
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
route := &netlink.Route{
|
||||||
|
Type: syscall.RTN_UNREACHABLE,
|
||||||
|
Table: tableID,
|
||||||
|
Family: getAddressFamily(prefix),
|
||||||
|
Dst: ipNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
return fmt.Errorf("netlink remove unreachable route: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeRoute removes a route from a specific routing table identified by tableID.
|
||||||
|
func removeRoute(prefix netip.Prefix, addr netip.Addr, intf string, tableID int) error {
|
||||||
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
route := &netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Table: tableID,
|
||||||
|
Family: getAddressFamily(prefix),
|
||||||
|
Dst: ipNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addNextHop(addr, intf, route); err != nil {
|
||||||
|
return fmt.Errorf("add gateway and device: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
return fmt.Errorf("netlink remove route: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flushRoutes(tableID, family int) error {
|
||||||
|
routes, err := netlink.RouteListFiltered(family, &netlink.Route{Table: tableID}, netlink.RT_FILTER_TABLE)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list routes from table %d: %w", tableID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *multierror.Error
|
||||||
|
for i := range routes {
|
||||||
|
route := routes[i]
|
||||||
|
// unreachable default routes don't come back with Dst set
|
||||||
|
if route.Gw == nil && route.Src == nil && route.Dst == nil {
|
||||||
|
if family == netlink.FAMILY_V4 {
|
||||||
|
routes[i].Dst = &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}
|
||||||
|
} else {
|
||||||
|
routes[i].Dst = &net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := netlink.RouteDel(&routes[i]); err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
result = multierror.Append(result, fmt.Errorf("failed to delete route %v from table %d: %w", routes[i], tableID, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableIPForwarding() error {
|
||||||
|
_, err := setSysctl(ipv4ForwardingPath, 1, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// entryExists checks if the specified ID or name already exists in the rt_tables file
|
// entryExists checks if the specified ID or name already exists in the rt_tables file
|
||||||
// and verifies if existing names start with "netbird_".
|
// and verifies if existing names start with "netbird_".
|
||||||
func entryExists(file *os.File, id int) (bool, error) {
|
func entryExists(file *os.File, id int) (bool, error) {
|
||||||
@@ -385,7 +442,7 @@ func addRule(params ruleParams) error {
|
|||||||
rule.Invert = params.invert
|
rule.Invert = params.invert
|
||||||
rule.SuppressPrefixlen = params.suppressPrefix
|
rule.SuppressPrefixlen = params.suppressPrefix
|
||||||
|
|
||||||
if err := netlink.RuleAdd(rule); err != nil {
|
if err := netlink.RuleAdd(rule); err != nil && !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
return fmt.Errorf("add routing rule: %w", err)
|
return fmt.Errorf("add routing rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,43 +459,116 @@ func removeRule(params ruleParams) error {
|
|||||||
rule.Priority = params.priority
|
rule.Priority = params.priority
|
||||||
rule.SuppressPrefixlen = params.suppressPrefix
|
rule.SuppressPrefixlen = params.suppressPrefix
|
||||||
|
|
||||||
if err := netlink.RuleDel(rule); err != nil {
|
if err := netlink.RuleDel(rule); err != nil && !errors.Is(err, syscall.ENOENT) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
return fmt.Errorf("remove routing rule: %w", err)
|
return fmt.Errorf("remove routing rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAllRules(params ruleParams) error {
|
|
||||||
for {
|
|
||||||
if err := removeRule(params); err != nil {
|
|
||||||
if errors.Is(err, syscall.ENOENT) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addNextHop adds the gateway and device to the route.
|
// addNextHop adds the gateway and device to the route.
|
||||||
func addNextHop(addr *string, intf *string, route *netlink.Route) error {
|
func addNextHop(addr netip.Addr, intf string, route *netlink.Route) error {
|
||||||
if addr != nil {
|
if addr.IsValid() {
|
||||||
ip := net.ParseIP(*addr)
|
route.Gw = addr.AsSlice()
|
||||||
if ip == nil {
|
if intf == "" {
|
||||||
return fmt.Errorf("parsing address %s failed", *addr)
|
intf = addr.Zone()
|
||||||
}
|
}
|
||||||
|
|
||||||
route.Gw = ip
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if intf != nil {
|
if intf != "" {
|
||||||
link, err := netlink.LinkByName(*intf)
|
link, err := netlink.LinkByName(intf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("set interface %s: %w", *intf, err)
|
return fmt.Errorf("set interface %s: %w", intf, err)
|
||||||
}
|
}
|
||||||
route.LinkIndex = link.Attrs().Index
|
route.LinkIndex = link.Attrs().Index
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAddressFamily(prefix netip.Prefix) int {
|
||||||
|
if prefix.Addr().Is4() {
|
||||||
|
return netlink.FAMILY_V4
|
||||||
|
}
|
||||||
|
return netlink.FAMILY_V6
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupSysctl configures sysctl settings for RP filtering and source validation.
|
||||||
|
func setupSysctl(wgIface *iface.WGIface) (map[string]int, error) {
|
||||||
|
keys := map[string]int{}
|
||||||
|
var result *multierror.Error
|
||||||
|
|
||||||
|
oldVal, err := setSysctl(srcValidMarkPath, 1, false)
|
||||||
|
if err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
} else {
|
||||||
|
keys[srcValidMarkPath] = oldVal
|
||||||
|
}
|
||||||
|
|
||||||
|
oldVal, err = setSysctl(rpFilterPath, 2, true)
|
||||||
|
if err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
} else {
|
||||||
|
keys[rpFilterPath] = oldVal
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
result = multierror.Append(result, fmt.Errorf("list interfaces: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, intf := range interfaces {
|
||||||
|
if intf.Name == "lo" || wgIface != nil && intf.Name == wgIface.Name() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i := fmt.Sprintf(rpFilterInterfacePath, intf.Name)
|
||||||
|
oldVal, err := setSysctl(i, 2, true)
|
||||||
|
if err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
} else {
|
||||||
|
keys[i] = oldVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, result.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSysctl sets a sysctl configuration, if onlyIfOne is true it will only set the new value if it's set to 1
|
||||||
|
func setSysctl(key string, desiredValue int, onlyIfOne bool) (int, error) {
|
||||||
|
path := fmt.Sprintf("/proc/sys/%s", strings.ReplaceAll(key, ".", "/"))
|
||||||
|
currentValue, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("read sysctl %s: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentV, err := strconv.Atoi(strings.TrimSpace(string(currentValue)))
|
||||||
|
if err != nil && len(currentValue) > 0 {
|
||||||
|
return -1, fmt.Errorf("convert current desiredValue to int: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentV == desiredValue || onlyIfOne && currentV != 1 {
|
||||||
|
return currentV, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gosec
|
||||||
|
if err := os.WriteFile(path, []byte(strconv.Itoa(desiredValue)), 0644); err != nil {
|
||||||
|
return currentV, fmt.Errorf("write sysctl %s: %w", key, err)
|
||||||
|
}
|
||||||
|
log.Debugf("Set sysctl %s from %d to %d", key, currentV, desiredValue)
|
||||||
|
|
||||||
|
return currentV, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupSysctl(originalSettings map[string]int) error {
|
||||||
|
var result *multierror.Error
|
||||||
|
|
||||||
|
for key, value := range originalSettings {
|
||||||
|
_, err := setSysctl(key, value, false)
|
||||||
|
if err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,34 +6,38 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gopacket/gopacket"
|
|
||||||
"github.com/gopacket/gopacket/layers"
|
|
||||||
"github.com/gopacket/gopacket/pcap"
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PacketExpectation struct {
|
var expectedVPNint = "wgtest0"
|
||||||
SrcIP net.IP
|
var expectedLoopbackInt = "lo"
|
||||||
DstIP net.IP
|
var expectedExternalInt = "dummyext0"
|
||||||
SrcPort int
|
var expectedInternalInt = "dummyint0"
|
||||||
DstPort int
|
|
||||||
UDP bool
|
func init() {
|
||||||
TCP bool
|
testCases = append(testCases, []testCase{
|
||||||
|
{
|
||||||
|
name: "To more specific route without custom dialer via physical interface",
|
||||||
|
destination: "10.10.0.2:53",
|
||||||
|
expectedInterface: expectedInternalInt,
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
expectedPacket: createPacketExpectation("192.168.1.1", 12345, "10.10.0.2", 53),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To more specific route (local) without custom dialer via physical interface",
|
||||||
|
destination: "127.0.10.1:53",
|
||||||
|
expectedInterface: expectedLoopbackInt,
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
expectedPacket: createPacketExpectation("127.0.0.1", 12345, "127.0.10.1", 53),
|
||||||
|
},
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEntryExists(t *testing.T) {
|
func TestEntryExists(t *testing.T) {
|
||||||
@@ -92,157 +96,7 @@ func TestEntryExists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoutingWithTables(t *testing.T) {
|
func createAndSetupDummyInterface(t *testing.T, interfaceName, ipAddressCIDR string) string {
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
destination string
|
|
||||||
captureInterface string
|
|
||||||
dialer *net.Dialer
|
|
||||||
packetExpectation PacketExpectation
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "To external host without fwmark via vpn",
|
|
||||||
destination: "192.0.2.1:53",
|
|
||||||
captureInterface: "wgtest0",
|
|
||||||
dialer: &net.Dialer{},
|
|
||||||
packetExpectation: createPacketExpectation("100.64.0.1", 12345, "192.0.2.1", 53),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "To external host with fwmark via physical interface",
|
|
||||||
destination: "192.0.2.1:53",
|
|
||||||
captureInterface: "dummyext0",
|
|
||||||
dialer: nbnet.NewDialer(),
|
|
||||||
packetExpectation: createPacketExpectation("192.168.0.1", 12345, "192.0.2.1", 53),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "To duplicate internal route with fwmark via physical interface",
|
|
||||||
destination: "10.0.0.1:53",
|
|
||||||
captureInterface: "dummyint0",
|
|
||||||
dialer: nbnet.NewDialer(),
|
|
||||||
packetExpectation: createPacketExpectation("192.168.1.1", 12345, "10.0.0.1", 53),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "To duplicate internal route without fwmark via physical interface", // local route takes precedence
|
|
||||||
destination: "10.0.0.1:53",
|
|
||||||
captureInterface: "dummyint0",
|
|
||||||
dialer: &net.Dialer{},
|
|
||||||
packetExpectation: createPacketExpectation("192.168.1.1", 12345, "10.0.0.1", 53),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "To unique vpn route with fwmark via physical interface",
|
|
||||||
destination: "172.16.0.1:53",
|
|
||||||
captureInterface: "dummyext0",
|
|
||||||
dialer: nbnet.NewDialer(),
|
|
||||||
packetExpectation: createPacketExpectation("192.168.0.1", 12345, "172.16.0.1", 53),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "To unique vpn route without fwmark via vpn",
|
|
||||||
destination: "172.16.0.1:53",
|
|
||||||
captureInterface: "wgtest0",
|
|
||||||
dialer: &net.Dialer{},
|
|
||||||
packetExpectation: createPacketExpectation("100.64.0.1", 12345, "172.16.0.1", 53),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "To more specific route without fwmark via vpn interface",
|
|
||||||
destination: "10.10.0.1:53",
|
|
||||||
captureInterface: "dummyint0",
|
|
||||||
dialer: &net.Dialer{},
|
|
||||||
packetExpectation: createPacketExpectation("192.168.1.1", 12345, "10.10.0.1", 53),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "To more specific route (local) without fwmark via physical interface",
|
|
||||||
destination: "127.0.10.1:53",
|
|
||||||
captureInterface: "lo",
|
|
||||||
dialer: &net.Dialer{},
|
|
||||||
packetExpectation: createPacketExpectation("127.0.0.1", 12345, "127.0.10.1", 53),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
wgIface, _, _ := setupTestEnv(t)
|
|
||||||
|
|
||||||
// default route exists in main table and vpn table
|
|
||||||
err := addToRouteTableIfNoExists(netip.MustParsePrefix("0.0.0.0/0"), wgIface.Address().IP.String(), wgIface.Name())
|
|
||||||
require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
|
|
||||||
|
|
||||||
// 10.0.0.0/8 route exists in main table and vpn table
|
|
||||||
err = addToRouteTableIfNoExists(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Address().IP.String(), wgIface.Name())
|
|
||||||
require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
|
|
||||||
|
|
||||||
// 10.10.0.0/24 more specific route exists in vpn table
|
|
||||||
err = addToRouteTableIfNoExists(netip.MustParsePrefix("10.10.0.0/24"), wgIface.Address().IP.String(), wgIface.Name())
|
|
||||||
require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
|
|
||||||
|
|
||||||
// 127.0.10.0/24 more specific route exists in vpn table
|
|
||||||
err = addToRouteTableIfNoExists(netip.MustParsePrefix("127.0.10.0/24"), wgIface.Address().IP.String(), wgIface.Name())
|
|
||||||
require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
|
|
||||||
|
|
||||||
// unique route in vpn table
|
|
||||||
err = addToRouteTableIfNoExists(netip.MustParsePrefix("172.16.0.0/16"), wgIface.Address().IP.String(), wgIface.Name())
|
|
||||||
require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
|
|
||||||
|
|
||||||
filter := createBPFFilter(tc.destination)
|
|
||||||
handle := startPacketCapture(t, tc.captureInterface, filter)
|
|
||||||
|
|
||||||
sendTestPacket(t, tc.destination, tc.packetExpectation.SrcPort, tc.dialer)
|
|
||||||
|
|
||||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
|
||||||
packet, err := packetSource.NextPacket()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
verifyPacket(t, packet, tc.packetExpectation)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPacket(t *testing.T, packet gopacket.Packet, exp PacketExpectation) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
ipLayer := packet.Layer(layers.LayerTypeIPv4)
|
|
||||||
require.NotNil(t, ipLayer, "Expected IPv4 layer not found in packet")
|
|
||||||
|
|
||||||
ip, ok := ipLayer.(*layers.IPv4)
|
|
||||||
require.True(t, ok, "Failed to cast to IPv4 layer")
|
|
||||||
|
|
||||||
// Convert both source and destination IP addresses to 16-byte representation
|
|
||||||
expectedSrcIP := exp.SrcIP.To16()
|
|
||||||
actualSrcIP := ip.SrcIP.To16()
|
|
||||||
assert.Equal(t, expectedSrcIP, actualSrcIP, "Source IP mismatch")
|
|
||||||
|
|
||||||
expectedDstIP := exp.DstIP.To16()
|
|
||||||
actualDstIP := ip.DstIP.To16()
|
|
||||||
assert.Equal(t, expectedDstIP, actualDstIP, "Destination IP mismatch")
|
|
||||||
|
|
||||||
if exp.UDP {
|
|
||||||
udpLayer := packet.Layer(layers.LayerTypeUDP)
|
|
||||||
require.NotNil(t, udpLayer, "Expected UDP layer not found in packet")
|
|
||||||
|
|
||||||
udp, ok := udpLayer.(*layers.UDP)
|
|
||||||
require.True(t, ok, "Failed to cast to UDP layer")
|
|
||||||
|
|
||||||
assert.Equal(t, layers.UDPPort(exp.SrcPort), udp.SrcPort, "UDP source port mismatch")
|
|
||||||
assert.Equal(t, layers.UDPPort(exp.DstPort), udp.DstPort, "UDP destination port mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
if exp.TCP {
|
|
||||||
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
|
||||||
require.NotNil(t, tcpLayer, "Expected TCP layer not found in packet")
|
|
||||||
|
|
||||||
tcp, ok := tcpLayer.(*layers.TCP)
|
|
||||||
require.True(t, ok, "Failed to cast to TCP layer")
|
|
||||||
|
|
||||||
assert.Equal(t, layers.TCPPort(exp.SrcPort), tcp.SrcPort, "TCP source port mismatch")
|
|
||||||
assert.Equal(t, layers.TCPPort(exp.DstPort), tcp.DstPort, "TCP destination port mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAndSetupDummyInterface(t *testing.T, interfaceName, ipAddressCIDR string) *netlink.Dummy {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
dummy := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: interfaceName}}
|
dummy := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: interfaceName}}
|
||||||
@@ -264,35 +118,52 @@ func createAndSetupDummyInterface(t *testing.T, interfaceName, ipAddressCIDR str
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dummy
|
t.Cleanup(func() {
|
||||||
|
err := netlink.LinkDel(dummy)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return dummy.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDummyRoute(t *testing.T, dstCIDR string, gw net.IP, linkIndex int) {
|
func addDummyRoute(t *testing.T, dstCIDR string, gw net.IP, intf string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
_, dstIPNet, err := net.ParseCIDR(dstCIDR)
|
_, dstIPNet, err := net.ParseCIDR(dstCIDR)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Handle existing routes with metric 0
|
||||||
|
var originalNexthop net.IP
|
||||||
|
var originalLinkIndex int
|
||||||
if dstIPNet.String() == "0.0.0.0/0" {
|
if dstIPNet.String() == "0.0.0.0/0" {
|
||||||
gw, linkIndex, err := fetchOriginalGateway(netlink.FAMILY_V4)
|
var err error
|
||||||
if err != nil {
|
originalNexthop, originalLinkIndex, err = fetchOriginalGateway(netlink.FAMILY_V4)
|
||||||
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
t.Logf("Failed to fetch original gateway: %v", err)
|
t.Logf("Failed to fetch original gateway: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle existing routes with metric 0
|
if originalNexthop != nil {
|
||||||
err = netlink.RouteDel(&netlink.Route{Dst: dstIPNet, Priority: 0})
|
err = netlink.RouteDel(&netlink.Route{Dst: dstIPNet, Priority: 0})
|
||||||
if err == nil {
|
switch {
|
||||||
t.Cleanup(func() {
|
case err != nil && !errors.Is(err, syscall.ESRCH):
|
||||||
err := netlink.RouteAdd(&netlink.Route{Dst: dstIPNet, Gw: gw, LinkIndex: linkIndex, Priority: 0})
|
t.Logf("Failed to delete route: %v", err)
|
||||||
if err != nil && !errors.Is(err, syscall.EEXIST) {
|
case err == nil:
|
||||||
t.Fatalf("Failed to add route: %v", err)
|
t.Cleanup(func() {
|
||||||
}
|
err := netlink.RouteAdd(&netlink.Route{Dst: dstIPNet, Gw: originalNexthop, LinkIndex: originalLinkIndex, Priority: 0})
|
||||||
})
|
if err != nil && !errors.Is(err, syscall.EEXIST) {
|
||||||
} else if !errors.Is(err, syscall.ESRCH) {
|
t.Fatalf("Failed to add route: %v", err)
|
||||||
t.Logf("Failed to delete route: %v", err)
|
}
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
t.Logf("Failed to delete route: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(intf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
linkIndex := link.Attrs().Index
|
||||||
|
|
||||||
route := &netlink.Route{
|
route := &netlink.Route{
|
||||||
Dst: dstIPNet,
|
Dst: dstIPNet,
|
||||||
Gw: gw,
|
Gw: gw,
|
||||||
@@ -307,9 +178,9 @@ func addDummyRoute(t *testing.T, dstCIDR string, gw net.IP, linkIndex int) {
|
|||||||
if err != nil && !errors.Is(err, syscall.EEXIST) {
|
if err != nil && !errors.Is(err, syscall.EEXIST) {
|
||||||
t.Fatalf("Failed to add route: %v", err)
|
t.Fatalf("Failed to add route: %v", err)
|
||||||
}
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchOriginalGateway returns the original gateway IP address and the interface index.
|
|
||||||
func fetchOriginalGateway(family int) (net.IP, int, error) {
|
func fetchOriginalGateway(family int) (net.IP, int, error) {
|
||||||
routes, err := netlink.RouteList(nil, family)
|
routes, err := netlink.RouteList(nil, family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -317,153 +188,20 @@ func fetchOriginalGateway(family int) (net.IP, int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route.Dst == nil {
|
if route.Dst == nil && route.Priority == 0 {
|
||||||
return route.Gw, route.LinkIndex, nil
|
return route.Gw, route.LinkIndex, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, 0, fmt.Errorf("default route not found")
|
return nil, 0, ErrRouteNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDummyInterfacesAndRoutes(t *testing.T) (string, string) {
|
func setupDummyInterfacesAndRoutes(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
defaultDummy := createAndSetupDummyInterface(t, "dummyext0", "192.168.0.1/24")
|
defaultDummy := createAndSetupDummyInterface(t, "dummyext0", "192.168.0.1/24")
|
||||||
addDummyRoute(t, "0.0.0.0/0", net.IPv4(192, 168, 0, 1), defaultDummy.Attrs().Index)
|
addDummyRoute(t, "0.0.0.0/0", net.IPv4(192, 168, 0, 1), defaultDummy)
|
||||||
|
|
||||||
otherDummy := createAndSetupDummyInterface(t, "dummyint0", "192.168.1.1/24")
|
otherDummy := createAndSetupDummyInterface(t, "dummyint0", "192.168.1.1/24")
|
||||||
addDummyRoute(t, "10.0.0.0/8", nil, otherDummy.Attrs().Index)
|
addDummyRoute(t, "10.0.0.0/8", net.IPv4(192, 168, 1, 1), otherDummy)
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err := netlink.LinkDel(defaultDummy)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
err = netlink.LinkDel(otherDummy)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
return defaultDummy.Name, otherDummy.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listenPort int) *iface.WGIface {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
peerPrivateKey, err := wgtypes.GeneratePrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
newNet, err := stdnet.NewNet(nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
wgInterface, err := iface.NewWGIFace(interfaceName, ipAddressCIDR, listenPort, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
|
||||||
require.NoError(t, err, "should create testing WireGuard interface")
|
|
||||||
|
|
||||||
err = wgInterface.Create()
|
|
||||||
require.NoError(t, err, "should create testing WireGuard interface")
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
wgInterface.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
return wgInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTestEnv(t *testing.T) (*iface.WGIface, string, string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
defaultDummy, otherDummy := setupDummyInterfacesAndRoutes(t)
|
|
||||||
|
|
||||||
wgIface := createWGInterface(t, "wgtest0", "100.64.0.1/24", 51820)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
assert.NoError(t, wgIface.Close())
|
|
||||||
})
|
|
||||||
|
|
||||||
err := setupRouting()
|
|
||||||
require.NoError(t, err, "setupRouting should not return err")
|
|
||||||
t.Cleanup(func() {
|
|
||||||
assert.NoError(t, cleanupRouting())
|
|
||||||
})
|
|
||||||
|
|
||||||
return wgIface, defaultDummy, otherDummy
|
|
||||||
}
|
|
||||||
|
|
||||||
func startPacketCapture(t *testing.T, intf, filter string) *pcap.Handle {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
inactive, err := pcap.NewInactiveHandle(intf)
|
|
||||||
require.NoError(t, err, "Failed to create inactive pcap handle")
|
|
||||||
defer inactive.CleanUp()
|
|
||||||
|
|
||||||
err = inactive.SetSnapLen(1600)
|
|
||||||
require.NoError(t, err, "Failed to set snap length on inactive handle")
|
|
||||||
|
|
||||||
err = inactive.SetTimeout(time.Second * 10)
|
|
||||||
require.NoError(t, err, "Failed to set timeout on inactive handle")
|
|
||||||
|
|
||||||
err = inactive.SetImmediateMode(true)
|
|
||||||
require.NoError(t, err, "Failed to set immediate mode on inactive handle")
|
|
||||||
|
|
||||||
handle, err := inactive.Activate()
|
|
||||||
require.NoError(t, err, "Failed to activate pcap handle")
|
|
||||||
t.Cleanup(handle.Close)
|
|
||||||
|
|
||||||
err = handle.SetBPFFilter(filter)
|
|
||||||
require.NoError(t, err, "Failed to set BPF filter")
|
|
||||||
|
|
||||||
return handle
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendTestPacket(t *testing.T, destination string, sourcePort int, dialer *net.Dialer) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if dialer == nil {
|
|
||||||
dialer = &net.Dialer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourcePort != 0 {
|
|
||||||
localUDPAddr := &net.UDPAddr{
|
|
||||||
IP: net.IPv4zero,
|
|
||||||
Port: sourcePort,
|
|
||||||
}
|
|
||||||
dialer.LocalAddr = localUDPAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := new(dns.Msg)
|
|
||||||
msg.Id = dns.Id()
|
|
||||||
msg.RecursionDesired = true
|
|
||||||
msg.Question = []dns.Question{
|
|
||||||
{Name: "example.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := dialer.Dial("udp", destination)
|
|
||||||
require.NoError(t, err, "Failed to dial UDP")
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
data, err := msg.Pack()
|
|
||||||
require.NoError(t, err, "Failed to pack DNS message")
|
|
||||||
|
|
||||||
_, err = conn.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "required key not available") {
|
|
||||||
t.Logf("Ignoring WireGuard key error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatalf("Failed to send DNS query: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createBPFFilter(destination string) string {
|
|
||||||
host, port, err := net.SplitHostPort(destination)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("udp and dst host %s and dst port %s", host, port)
|
|
||||||
}
|
|
||||||
return "udp"
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPacketExpectation(srcIP string, srcPort int, dstIP string, dstPort int) PacketExpectation {
|
|
||||||
return PacketExpectation{
|
|
||||||
SrcIP: net.ParseIP(srcIP),
|
|
||||||
DstIP: net.ParseIP(dstIP),
|
|
||||||
SrcPort: srcPort,
|
|
||||||
DstPort: dstPort,
|
|
||||||
UDP: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
//go:build !android
|
|
||||||
|
|
||||||
//nolint:unused
|
|
||||||
package routemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/libp2p/go-netroute"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errRouteNotFound = fmt.Errorf("route not found")
|
|
||||||
|
|
||||||
func genericAddRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
|
||||||
defaultGateway, err := getExistingRIBRouteGateway(defaultv4)
|
|
||||||
if err != nil && !errors.Is(err, errRouteNotFound) {
|
|
||||||
return fmt.Errorf("get existing route gateway: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := netip.MustParseAddr(defaultGateway.String())
|
|
||||||
|
|
||||||
if !prefix.Contains(addr) {
|
|
||||||
log.Debugf("Skipping adding a new route for gateway %s because it is not in the network %s", addr, prefix)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gatewayPrefix := netip.PrefixFrom(addr, 32)
|
|
||||||
|
|
||||||
ok, err := existsInRouteTable(gatewayPrefix)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to check if there is an existing route for gateway %s. error: %s", gatewayPrefix, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
log.Debugf("Skipping adding a new route for gateway %s because it already exists", gatewayPrefix)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gatewayHop, err := getExistingRIBRouteGateway(gatewayPrefix)
|
|
||||||
if err != nil && !errors.Is(err, errRouteNotFound) {
|
|
||||||
return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err)
|
|
||||||
}
|
|
||||||
log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, gatewayHop)
|
|
||||||
return genericAddToRouteTable(gatewayPrefix, gatewayHop.String(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func genericAddToRouteTableIfNoExists(prefix netip.Prefix, addr string, intf string) error {
|
|
||||||
ok, err := existsInRouteTable(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("exists in route table: %w", err)
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
log.Warnf("Skipping adding a new route for network %s because it already exists", prefix)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err = isSubRange(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("sub range: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
err := genericAddRouteForCurrentDefaultGateway(prefix)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Unable to add route for current default gateway route. Will proceed without it. error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return genericAddToRouteTable(prefix, addr, intf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func genericRemoveFromRouteTableIfNonSystem(prefix netip.Prefix, addr string, intf string) error {
|
|
||||||
return genericRemoveFromRouteTable(prefix, addr, intf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func genericAddToRouteTable(prefix netip.Prefix, addr, _ string) error {
|
|
||||||
cmd := exec.Command("route", "add", prefix.String(), addr)
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("add route: %w", err)
|
|
||||||
}
|
|
||||||
log.Debugf(string(out))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genericRemoveFromRouteTable(prefix netip.Prefix, addr, _ string) error {
|
|
||||||
args := []string{"delete", prefix.String()}
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
args = append(args, addr)
|
|
||||||
}
|
|
||||||
cmd := exec.Command("route", args...)
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("remove route: %w", err)
|
|
||||||
}
|
|
||||||
log.Debugf(string(out))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) {
|
|
||||||
r, err := netroute.New()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("new netroute: %w", err)
|
|
||||||
}
|
|
||||||
_, gateway, preferredSrc, err := r.Route(prefix.Addr().AsSlice())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Getting routes returned an error: %v", err)
|
|
||||||
return nil, errRouteNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if gateway == nil {
|
|
||||||
return preferredSrc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return gateway, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
|
||||||
routes, err := getRoutesFromTable()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("get routes from table: %w", err)
|
|
||||||
}
|
|
||||||
for _, tableRoute := range routes {
|
|
||||||
if tableRoute == prefix {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSubRange(prefix netip.Prefix) (bool, error) {
|
|
||||||
routes, err := getRoutesFromTable()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("get routes from table: %w", err)
|
|
||||||
}
|
|
||||||
for _, tableRoute := range routes {
|
|
||||||
if isPrefixSupported(tableRoute) && tableRoute.Contains(prefix.Addr()) && tableRoute.Bits() < prefix.Bits() {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
//go:build !linux || android
|
//go:build !linux && !ios
|
||||||
|
|
||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupRouting() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupRouting() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func enableIPForwarding() error {
|
func enableIPForwarding() error {
|
||||||
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addVPNRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
return genericAddVPNRoute(prefix, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeVPNRoute(prefix netip.Prefix, intf string) error {
|
||||||
|
return genericRemoveVPNRoute(prefix, intf)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
//go:build !linux || android
|
|
||||||
|
|
||||||
package routemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsSubRange(t *testing.T) {
|
|
||||||
addresses, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var subRangeAddressPrefixes []netip.Prefix
|
|
||||||
var nonSubRangeAddressPrefixes []netip.Prefix
|
|
||||||
for _, address := range addresses {
|
|
||||||
p := netip.MustParsePrefix(address.String())
|
|
||||||
if !p.Addr().IsLoopback() && p.Addr().Is4() && p.Bits() < 32 {
|
|
||||||
p2 := netip.PrefixFrom(p.Masked().Addr(), p.Bits()+1)
|
|
||||||
subRangeAddressPrefixes = append(subRangeAddressPrefixes, p2)
|
|
||||||
nonSubRangeAddressPrefixes = append(nonSubRangeAddressPrefixes, p.Masked())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, prefix := range subRangeAddressPrefixes {
|
|
||||||
isSubRangePrefix, err := isSubRange(prefix)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("shouldn't return error when checking if address is sub-range: ", err)
|
|
||||||
}
|
|
||||||
if !isSubRangePrefix {
|
|
||||||
t.Fatalf("address %s should be sub-range of an existing route in the table", prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, prefix := range nonSubRangeAddressPrefixes {
|
|
||||||
isSubRangePrefix, err := isSubRange(prefix)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("shouldn't return error when checking if address is sub-range: ", err)
|
|
||||||
}
|
|
||||||
if isSubRangePrefix {
|
|
||||||
t.Fatalf("address %s should not be sub-range of an existing route in the table", prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExistsInRouteTable(t *testing.T) {
|
|
||||||
require.NoError(t, setupRouting())
|
|
||||||
t.Cleanup(func() {
|
|
||||||
assert.NoError(t, cleanupRouting())
|
|
||||||
})
|
|
||||||
|
|
||||||
addresses, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addressPrefixes []netip.Prefix
|
|
||||||
for _, address := range addresses {
|
|
||||||
p := netip.MustParsePrefix(address.String())
|
|
||||||
if p.Addr().Is4() {
|
|
||||||
addressPrefixes = append(addressPrefixes, p.Masked())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, prefix := range addressPrefixes {
|
|
||||||
exists, err := existsInRouteTable(prefix)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("shouldn't return error when checking if address exists in route table: ", err)
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
t.Fatalf("address %s should exist in route table", prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
//go:build !android
|
//go:build !android && !ios
|
||||||
|
|
||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -22,47 +22,9 @@ import (
|
|||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertWGOutInterface(t *testing.T, prefix netip.Prefix, wgIface *iface.WGIface, invert bool) {
|
type dialer interface {
|
||||||
t.Helper()
|
Dial(network, address string) (net.Conn, error)
|
||||||
|
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
outIntf, err := getOutgoingInterfaceLinux(prefix.Addr().String())
|
|
||||||
require.NoError(t, err, "getOutgoingInterfaceLinux should not return error")
|
|
||||||
if invert {
|
|
||||||
require.NotEqual(t, wgIface.Name(), outIntf, "outgoing interface should not be the wireguard interface")
|
|
||||||
} else {
|
|
||||||
require.Equal(t, wgIface.Name(), outIntf, "outgoing interface should be the wireguard interface")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixGateway, err := getExistingRIBRouteGateway(prefix)
|
|
||||||
require.NoError(t, err, "getExistingRIBRouteGateway should not return err")
|
|
||||||
if invert {
|
|
||||||
assert.NotEqual(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should not point to wireguard interface IP")
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOutgoingInterfaceLinux(destination string) (string, error) {
|
|
||||||
cmd := exec.Command("ip", "route", "get", destination)
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("executing ip route get: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseOutgoingInterface(string(output)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOutgoingInterface(routeGetOutput string) string {
|
|
||||||
fields := strings.Fields(routeGetOutput)
|
|
||||||
for i, field := range fields {
|
|
||||||
if field == "dev" && i+1 < len(fields) {
|
|
||||||
return fields[i+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRemoveRoutes(t *testing.T) {
|
func TestAddRemoveRoutes(t *testing.T) {
|
||||||
@@ -99,14 +61,14 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
|
|
||||||
err = wgInterface.Create()
|
err = wgInterface.Create()
|
||||||
require.NoError(t, err, "should create testing wireguard interface")
|
require.NoError(t, err, "should create testing wireguard interface")
|
||||||
|
_, _, err = setupRouting(nil, wgInterface)
|
||||||
require.NoError(t, setupRouting())
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
assert.NoError(t, cleanupRouting())
|
assert.NoError(t, cleanupRouting())
|
||||||
})
|
})
|
||||||
|
|
||||||
err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.Address().IP.String(), wgInterface.Name())
|
err = genericAddVPNRoute(testCase.prefix, wgInterface.Name())
|
||||||
require.NoError(t, err, "addToRouteTableIfNoExists should not return err")
|
require.NoError(t, err, "genericAddVPNRoute should not return err")
|
||||||
|
|
||||||
if testCase.shouldRouteToWireguard {
|
if testCase.shouldRouteToWireguard {
|
||||||
assertWGOutInterface(t, testCase.prefix, wgInterface, false)
|
assertWGOutInterface(t, testCase.prefix, wgInterface, false)
|
||||||
@@ -116,13 +78,13 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
exists, err := existsInRouteTable(testCase.prefix)
|
exists, err := existsInRouteTable(testCase.prefix)
|
||||||
require.NoError(t, err, "existsInRouteTable should not return err")
|
require.NoError(t, err, "existsInRouteTable should not return err")
|
||||||
if exists && testCase.shouldRouteToWireguard {
|
if exists && testCase.shouldRouteToWireguard {
|
||||||
err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String(), wgInterface.Name())
|
err = genericRemoveVPNRoute(testCase.prefix, wgInterface.Name())
|
||||||
require.NoError(t, err, "removeFromRouteTableIfNonSystem should not return err")
|
require.NoError(t, err, "genericRemoveVPNRoute should not return err")
|
||||||
|
|
||||||
prefixGateway, err := getExistingRIBRouteGateway(testCase.prefix)
|
prefixGateway, _, err := getNextHop(testCase.prefix.Addr())
|
||||||
require.NoError(t, err, "getExistingRIBRouteGateway should not return err")
|
require.NoError(t, err, "getNextHop should not return err")
|
||||||
|
|
||||||
internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
internetGateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if testCase.shouldBeRemoved {
|
if testCase.shouldBeRemoved {
|
||||||
@@ -135,12 +97,12 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetExistingRIBRouteGateway(t *testing.T) {
|
func TestGetNextHop(t *testing.T) {
|
||||||
gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
gateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
}
|
}
|
||||||
if gateway == nil {
|
if !gateway.IsValid() {
|
||||||
t.Fatal("should return a gateway")
|
t.Fatal("should return a gateway")
|
||||||
}
|
}
|
||||||
addresses, err := net.InterfaceAddrs()
|
addresses, err := net.InterfaceAddrs()
|
||||||
@@ -162,11 +124,11 @@ func TestGetExistingRIBRouteGateway(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localIP, err := getExistingRIBRouteGateway(testingPrefix)
|
localIP, _, err := getNextHop(testingPrefix.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error: ", err)
|
t.Fatal("shouldn't return error: ", err)
|
||||||
}
|
}
|
||||||
if localIP == nil {
|
if !localIP.IsValid() {
|
||||||
t.Fatal("should return a gateway for local network")
|
t.Fatal("should return a gateway for local network")
|
||||||
}
|
}
|
||||||
if localIP.String() == gateway.String() {
|
if localIP.String() == gateway.String() {
|
||||||
@@ -177,8 +139,8 @@ func TestGetExistingRIBRouteGateway(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
|
func TestAddExistAndRemoveRoute(t *testing.T) {
|
||||||
defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
defaultGateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
t.Log("defaultGateway: ", defaultGateway)
|
t.Log("defaultGateway: ", defaultGateway)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
@@ -238,21 +200,14 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
|
|||||||
err = wgInterface.Create()
|
err = wgInterface.Create()
|
||||||
require.NoError(t, err, "should create testing wireguard interface")
|
require.NoError(t, err, "should create testing wireguard interface")
|
||||||
|
|
||||||
require.NoError(t, setupRouting())
|
|
||||||
t.Cleanup(func() {
|
|
||||||
assert.NoError(t, cleanupRouting())
|
|
||||||
})
|
|
||||||
|
|
||||||
MockAddr := wgInterface.Address().IP.String()
|
|
||||||
|
|
||||||
// Prepare the environment
|
// Prepare the environment
|
||||||
if testCase.preExistingPrefix.IsValid() {
|
if testCase.preExistingPrefix.IsValid() {
|
||||||
err := addToRouteTableIfNoExists(testCase.preExistingPrefix, MockAddr, wgInterface.Name())
|
err := genericAddVPNRoute(testCase.preExistingPrefix, wgInterface.Name())
|
||||||
require.NoError(t, err, "should not return err when adding pre-existing route")
|
require.NoError(t, err, "should not return err when adding pre-existing route")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the route
|
// Add the route
|
||||||
err = addToRouteTableIfNoExists(testCase.prefix, MockAddr, wgInterface.Name())
|
err = genericAddVPNRoute(testCase.prefix, wgInterface.Name())
|
||||||
require.NoError(t, err, "should not return err when adding route")
|
require.NoError(t, err, "should not return err when adding route")
|
||||||
|
|
||||||
if testCase.shouldAddRoute {
|
if testCase.shouldAddRoute {
|
||||||
@@ -262,7 +217,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
|
|||||||
require.True(t, ok, "route should exist")
|
require.True(t, ok, "route should exist")
|
||||||
|
|
||||||
// remove route again if added
|
// remove route again if added
|
||||||
err = removeFromRouteTableIfNonSystem(testCase.prefix, MockAddr, wgInterface.Name())
|
err = genericRemoveVPNRoute(testCase.prefix, wgInterface.Name())
|
||||||
require.NoError(t, err, "should not return err")
|
require.NoError(t, err, "should not return err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,11 +227,176 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
|
|||||||
t.Log("Buffer string: ", buf.String())
|
t.Log("Buffer string: ", buf.String())
|
||||||
require.NoError(t, err, "should not return err")
|
require.NoError(t, err, "should not return err")
|
||||||
|
|
||||||
// Linux uses a separate routing table, so the route can exist in both tables.
|
if !strings.Contains(buf.String(), "because it already exists") {
|
||||||
// The main routing table takes precedence over the wireguard routing table.
|
|
||||||
if !strings.Contains(buf.String(), "because it already exists") && runtime.GOOS != "linux" {
|
|
||||||
require.False(t, ok, "route should not exist")
|
require.False(t, ok, "route should not exist")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsSubRange(t *testing.T) {
|
||||||
|
addresses, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var subRangeAddressPrefixes []netip.Prefix
|
||||||
|
var nonSubRangeAddressPrefixes []netip.Prefix
|
||||||
|
for _, address := range addresses {
|
||||||
|
p := netip.MustParsePrefix(address.String())
|
||||||
|
if !p.Addr().IsLoopback() && p.Addr().Is4() && p.Bits() < 32 {
|
||||||
|
p2 := netip.PrefixFrom(p.Masked().Addr(), p.Bits()+1)
|
||||||
|
subRangeAddressPrefixes = append(subRangeAddressPrefixes, p2)
|
||||||
|
nonSubRangeAddressPrefixes = append(nonSubRangeAddressPrefixes, p.Masked())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range subRangeAddressPrefixes {
|
||||||
|
isSubRangePrefix, err := isSubRange(prefix)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when checking if address is sub-range: ", err)
|
||||||
|
}
|
||||||
|
if !isSubRangePrefix {
|
||||||
|
t.Fatalf("address %s should be sub-range of an existing route in the table", prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range nonSubRangeAddressPrefixes {
|
||||||
|
isSubRangePrefix, err := isSubRange(prefix)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when checking if address is sub-range: ", err)
|
||||||
|
}
|
||||||
|
if isSubRangePrefix {
|
||||||
|
t.Fatalf("address %s should not be sub-range of an existing route in the table", prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExistsInRouteTable(t *testing.T) {
|
||||||
|
addresses, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressPrefixes []netip.Prefix
|
||||||
|
for _, address := range addresses {
|
||||||
|
p := netip.MustParsePrefix(address.String())
|
||||||
|
if p.Addr().Is6() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Windows sometimes has hidden interface link local addrs that don't turn up on any interface
|
||||||
|
if runtime.GOOS == "windows" && p.Addr().IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Linux loopback 127/8 is in the local table, not in the main table and always takes precedence
|
||||||
|
if runtime.GOOS == "linux" && p.Addr().IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addressPrefixes = append(addressPrefixes, p.Masked())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range addressPrefixes {
|
||||||
|
exists, err := existsInRouteTable(prefix)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when checking if address exists in route table: ", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("address %s should exist in route table", prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listenPort int) *iface.WGIface {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
peerPrivateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wgInterface, err := iface.NewWGIFace(interfaceName, ipAddressCIDR, listenPort, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
|
require.NoError(t, err, "should create testing WireGuard interface")
|
||||||
|
|
||||||
|
err = wgInterface.Create()
|
||||||
|
require.NoError(t, err, "should create testing WireGuard interface")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
wgInterface.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
return wgInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestEnv(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
setupDummyInterfacesAndRoutes(t)
|
||||||
|
|
||||||
|
wgIface := createWGInterface(t, expectedVPNint, "100.64.0.1/24", 51820)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.NoError(t, wgIface.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err := setupRouting(nil, wgIface)
|
||||||
|
require.NoError(t, err, "setupRouting should not return err")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.NoError(t, cleanupRouting())
|
||||||
|
})
|
||||||
|
|
||||||
|
// default route exists in main table and vpn table
|
||||||
|
err = addVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), wgIface.Name())
|
||||||
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err = removeVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), wgIface.Name())
|
||||||
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
|
})
|
||||||
|
|
||||||
|
// 10.0.0.0/8 route exists in main table and vpn table
|
||||||
|
err = addVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Name())
|
||||||
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err = removeVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Name())
|
||||||
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
|
})
|
||||||
|
|
||||||
|
// 10.10.0.0/24 more specific route exists in vpn table
|
||||||
|
err = addVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), wgIface.Name())
|
||||||
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err = removeVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), wgIface.Name())
|
||||||
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
|
})
|
||||||
|
|
||||||
|
// 127.0.10.0/24 more specific route exists in vpn table
|
||||||
|
err = addVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), wgIface.Name())
|
||||||
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err = removeVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), wgIface.Name())
|
||||||
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
|
})
|
||||||
|
|
||||||
|
// unique route in vpn table
|
||||||
|
err = addVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), wgIface.Name())
|
||||||
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err = removeVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), wgIface.Name())
|
||||||
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertWGOutInterface(t *testing.T, prefix netip.Prefix, wgIface *iface.WGIface, invert bool) {
|
||||||
|
t.Helper()
|
||||||
|
if runtime.GOOS == "linux" && prefix.Addr().IsLoopback() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixGateway, _, err := getNextHop(prefix.Addr())
|
||||||
|
require.NoError(t, err, "getNextHop should not return err")
|
||||||
|
if invert {
|
||||||
|
assert.NotEqual(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should not point to wireguard interface IP")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP")
|
||||||
|
}
|
||||||
|
}
|
||||||
234
client/internal/routemanager/systemops_unix_test.go
Normal file
234
client/internal/routemanager/systemops_unix_test.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
//go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || netbsd || dragonfly
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gopacket/gopacket"
|
||||||
|
"github.com/gopacket/gopacket/layers"
|
||||||
|
"github.com/gopacket/gopacket/pcap"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketExpectation struct {
|
||||||
|
SrcIP net.IP
|
||||||
|
DstIP net.IP
|
||||||
|
SrcPort int
|
||||||
|
DstPort int
|
||||||
|
UDP bool
|
||||||
|
TCP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
destination string
|
||||||
|
expectedInterface string
|
||||||
|
dialer dialer
|
||||||
|
expectedPacket PacketExpectation
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases = []testCase{
|
||||||
|
{
|
||||||
|
name: "To external host without custom dialer via vpn",
|
||||||
|
destination: "192.0.2.1:53",
|
||||||
|
expectedInterface: expectedVPNint,
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
expectedPacket: createPacketExpectation("100.64.0.1", 12345, "192.0.2.1", 53),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To external host with custom dialer via physical interface",
|
||||||
|
destination: "192.0.2.1:53",
|
||||||
|
expectedInterface: expectedExternalInt,
|
||||||
|
dialer: nbnet.NewDialer(),
|
||||||
|
expectedPacket: createPacketExpectation("192.168.0.1", 12345, "192.0.2.1", 53),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "To duplicate internal route with custom dialer via physical interface",
|
||||||
|
destination: "10.0.0.2:53",
|
||||||
|
expectedInterface: expectedInternalInt,
|
||||||
|
dialer: nbnet.NewDialer(),
|
||||||
|
expectedPacket: createPacketExpectation("192.168.1.1", 12345, "10.0.0.2", 53),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To duplicate internal route without custom dialer via physical interface", // local route takes precedence
|
||||||
|
destination: "10.0.0.2:53",
|
||||||
|
expectedInterface: expectedInternalInt,
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
expectedPacket: createPacketExpectation("192.168.1.1", 12345, "10.0.0.2", 53),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "To unique vpn route with custom dialer via physical interface",
|
||||||
|
destination: "172.16.0.2:53",
|
||||||
|
expectedInterface: expectedExternalInt,
|
||||||
|
dialer: nbnet.NewDialer(),
|
||||||
|
expectedPacket: createPacketExpectation("192.168.0.1", 12345, "172.16.0.2", 53),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To unique vpn route without custom dialer via vpn",
|
||||||
|
destination: "172.16.0.2:53",
|
||||||
|
expectedInterface: expectedVPNint,
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
expectedPacket: createPacketExpectation("100.64.0.1", 12345, "172.16.0.2", 53),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouting(t *testing.T) {
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
setupTestEnv(t)
|
||||||
|
|
||||||
|
filter := createBPFFilter(tc.destination)
|
||||||
|
handle := startPacketCapture(t, tc.expectedInterface, filter)
|
||||||
|
|
||||||
|
sendTestPacket(t, tc.destination, tc.expectedPacket.SrcPort, tc.dialer)
|
||||||
|
|
||||||
|
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
||||||
|
packet, err := packetSource.NextPacket()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
verifyPacket(t, packet, tc.expectedPacket)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPacketExpectation(srcIP string, srcPort int, dstIP string, dstPort int) PacketExpectation {
|
||||||
|
return PacketExpectation{
|
||||||
|
SrcIP: net.ParseIP(srcIP),
|
||||||
|
DstIP: net.ParseIP(dstIP),
|
||||||
|
SrcPort: srcPort,
|
||||||
|
DstPort: dstPort,
|
||||||
|
UDP: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startPacketCapture(t *testing.T, intf, filter string) *pcap.Handle {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
inactive, err := pcap.NewInactiveHandle(intf)
|
||||||
|
require.NoError(t, err, "Failed to create inactive pcap handle")
|
||||||
|
defer inactive.CleanUp()
|
||||||
|
|
||||||
|
err = inactive.SetSnapLen(1600)
|
||||||
|
require.NoError(t, err, "Failed to set snap length on inactive handle")
|
||||||
|
|
||||||
|
err = inactive.SetTimeout(time.Second * 10)
|
||||||
|
require.NoError(t, err, "Failed to set timeout on inactive handle")
|
||||||
|
|
||||||
|
err = inactive.SetImmediateMode(true)
|
||||||
|
require.NoError(t, err, "Failed to set immediate mode on inactive handle")
|
||||||
|
|
||||||
|
handle, err := inactive.Activate()
|
||||||
|
require.NoError(t, err, "Failed to activate pcap handle")
|
||||||
|
t.Cleanup(handle.Close)
|
||||||
|
|
||||||
|
err = handle.SetBPFFilter(filter)
|
||||||
|
require.NoError(t, err, "Failed to set BPF filter")
|
||||||
|
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendTestPacket(t *testing.T, destination string, sourcePort int, dialer dialer) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if dialer == nil {
|
||||||
|
dialer = &net.Dialer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourcePort != 0 {
|
||||||
|
localUDPAddr := &net.UDPAddr{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Port: sourcePort,
|
||||||
|
}
|
||||||
|
switch dialer := dialer.(type) {
|
||||||
|
case *nbnet.Dialer:
|
||||||
|
dialer.LocalAddr = localUDPAddr
|
||||||
|
case *net.Dialer:
|
||||||
|
dialer.LocalAddr = localUDPAddr
|
||||||
|
default:
|
||||||
|
t.Fatal("Unsupported dialer type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.Id = dns.Id()
|
||||||
|
msg.RecursionDesired = true
|
||||||
|
msg.Question = []dns.Question{
|
||||||
|
{Name: "example.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dialer.Dial("udp", destination)
|
||||||
|
require.NoError(t, err, "Failed to dial UDP")
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
data, err := msg.Pack()
|
||||||
|
require.NoError(t, err, "Failed to pack DNS message")
|
||||||
|
|
||||||
|
_, err = conn.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "required key not available") {
|
||||||
|
t.Logf("Ignoring WireGuard key error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("Failed to send DNS query: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBPFFilter(destination string) string {
|
||||||
|
host, port, err := net.SplitHostPort(destination)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("udp and dst host %s and dst port %s", host, port)
|
||||||
|
}
|
||||||
|
return "udp"
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPacket(t *testing.T, packet gopacket.Packet, exp PacketExpectation) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ipLayer := packet.Layer(layers.LayerTypeIPv4)
|
||||||
|
require.NotNil(t, ipLayer, "Expected IPv4 layer not found in packet")
|
||||||
|
|
||||||
|
ip, ok := ipLayer.(*layers.IPv4)
|
||||||
|
require.True(t, ok, "Failed to cast to IPv4 layer")
|
||||||
|
|
||||||
|
// Convert both source and destination IP addresses to 16-byte representation
|
||||||
|
expectedSrcIP := exp.SrcIP.To16()
|
||||||
|
actualSrcIP := ip.SrcIP.To16()
|
||||||
|
assert.Equal(t, expectedSrcIP, actualSrcIP, "Source IP mismatch")
|
||||||
|
|
||||||
|
expectedDstIP := exp.DstIP.To16()
|
||||||
|
actualDstIP := ip.DstIP.To16()
|
||||||
|
assert.Equal(t, expectedDstIP, actualDstIP, "Destination IP mismatch")
|
||||||
|
|
||||||
|
if exp.UDP {
|
||||||
|
udpLayer := packet.Layer(layers.LayerTypeUDP)
|
||||||
|
require.NotNil(t, udpLayer, "Expected UDP layer not found in packet")
|
||||||
|
|
||||||
|
udp, ok := udpLayer.(*layers.UDP)
|
||||||
|
require.True(t, ok, "Failed to cast to UDP layer")
|
||||||
|
|
||||||
|
assert.Equal(t, layers.UDPPort(exp.SrcPort), udp.SrcPort, "UDP source port mismatch")
|
||||||
|
assert.Equal(t, layers.UDPPort(exp.DstPort), udp.DstPort, "UDP destination port mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp.TCP {
|
||||||
|
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
||||||
|
require.NotNil(t, tcpLayer, "Expected TCP layer not found in packet")
|
||||||
|
|
||||||
|
tcp, ok := tcpLayer.(*layers.TCP)
|
||||||
|
require.True(t, ok, "Failed to cast to TCP layer")
|
||||||
|
|
||||||
|
assert.Equal(t, layers.TCPPort(exp.SrcPort), tcp.SrcPort, "TCP source port mismatch")
|
||||||
|
assert.Equal(t, layers.TCPPort(exp.DstPort), tcp.DstPort, "TCP destination port mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/yusufpapurcu/wmi"
|
"github.com/yusufpapurcu/wmi"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Win32_IP4RouteTable struct {
|
type Win32_IP4RouteTable struct {
|
||||||
@@ -16,6 +21,16 @@ type Win32_IP4RouteTable struct {
|
|||||||
Mask string
|
Mask string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var routeManager *RouteManager
|
||||||
|
|
||||||
|
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupRouting() error {
|
||||||
|
return cleanupRoutingWithRouteManager(routeManager)
|
||||||
|
}
|
||||||
|
|
||||||
func getRoutesFromTable() ([]netip.Prefix, error) {
|
func getRoutesFromTable() ([]netip.Prefix, error) {
|
||||||
var routes []Win32_IP4RouteTable
|
var routes []Win32_IP4RouteTable
|
||||||
query := "SELECT Destination, Mask FROM Win32_IP4RouteTable"
|
query := "SELECT Destination, Mask FROM Win32_IP4RouteTable"
|
||||||
@@ -48,10 +63,85 @@ func getRoutesFromTable() ([]netip.Prefix, error) {
|
|||||||
return prefixList, nil
|
return prefixList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, intf string) error {
|
func addRoutePowershell(prefix netip.Prefix, nexthop netip.Addr, intf, intfIdx string) error {
|
||||||
return genericAddToRouteTableIfNoExists(prefix, addr, intf)
|
destinationPrefix := prefix.String()
|
||||||
|
psCmd := "New-NetRoute"
|
||||||
|
|
||||||
|
addressFamily := "IPv4"
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
addressFamily = "IPv6"
|
||||||
|
}
|
||||||
|
|
||||||
|
script := fmt.Sprintf(
|
||||||
|
`%s -AddressFamily "%s" -DestinationPrefix "%s" -Confirm:$False -ErrorAction Stop -PolicyStore ActiveStore`,
|
||||||
|
psCmd, addressFamily, destinationPrefix,
|
||||||
|
)
|
||||||
|
|
||||||
|
if intfIdx != "" {
|
||||||
|
script = fmt.Sprintf(
|
||||||
|
`%s -InterfaceIndex %s`, script, intfIdx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
script = fmt.Sprintf(
|
||||||
|
`%s -InterfaceAlias "%s"`, script, intf,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nexthop.IsValid() {
|
||||||
|
script = fmt.Sprintf(
|
||||||
|
`%s -NextHop "%s"`, script, nexthop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command("powershell", "-Command", script).CombinedOutput()
|
||||||
|
log.Tracef("PowerShell %s: %s", script, string(out))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("PowerShell add route: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string, intf string) error {
|
func addRouteCmd(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
||||||
return genericRemoveFromRouteTableIfNonSystem(prefix, addr, intf)
|
args := []string{"add", prefix.String(), nexthop.Unmap().String()}
|
||||||
|
|
||||||
|
out, err := exec.Command("route", args...).CombinedOutput()
|
||||||
|
|
||||||
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("route add: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
||||||
|
var intfIdx string
|
||||||
|
if nexthop.Zone() != "" {
|
||||||
|
intfIdx = nexthop.Zone()
|
||||||
|
nexthop.WithZone("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Powershell doesn't support adding routes without an interface but allows to add interface by name
|
||||||
|
if intf != "" || intfIdx != "" {
|
||||||
|
return addRoutePowershell(prefix, nexthop, intf, intfIdx)
|
||||||
|
}
|
||||||
|
return addRouteCmd(prefix, nexthop, intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
||||||
|
args := []string{"delete", prefix.String()}
|
||||||
|
if nexthop.IsValid() {
|
||||||
|
nexthop.WithZone("")
|
||||||
|
args = append(args, nexthop.Unmap().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command("route", args...).CombinedOutput()
|
||||||
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("remove route: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
289
client/internal/routemanager/systemops_windows_test.go
Normal file
289
client/internal/routemanager/systemops_windows_test.go
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var expectedExtInt = "Ethernet1"
|
||||||
|
|
||||||
|
type RouteInfo struct {
|
||||||
|
NextHop string `json:"nexthop"`
|
||||||
|
InterfaceAlias string `json:"interfacealias"`
|
||||||
|
RouteMetric int `json:"routemetric"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FindNetRouteOutput struct {
|
||||||
|
IPAddress string `json:"IPAddress"`
|
||||||
|
InterfaceIndex int `json:"InterfaceIndex"`
|
||||||
|
InterfaceAlias string `json:"InterfaceAlias"`
|
||||||
|
AddressFamily int `json:"AddressFamily"`
|
||||||
|
NextHop string `json:"NextHop"`
|
||||||
|
DestinationPrefix string `json:"DestinationPrefix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
destination string
|
||||||
|
expectedSourceIP string
|
||||||
|
expectedDestPrefix string
|
||||||
|
expectedNextHop string
|
||||||
|
expectedInterface string
|
||||||
|
dialer dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedVPNint = "wgtest0"
|
||||||
|
|
||||||
|
var testCases = []testCase{
|
||||||
|
{
|
||||||
|
name: "To external host without custom dialer via vpn",
|
||||||
|
destination: "192.0.2.1:53",
|
||||||
|
expectedSourceIP: "100.64.0.1",
|
||||||
|
expectedDestPrefix: "128.0.0.0/1",
|
||||||
|
expectedNextHop: "0.0.0.0",
|
||||||
|
expectedInterface: "wgtest0",
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To external host with custom dialer via physical interface",
|
||||||
|
destination: "192.0.2.1:53",
|
||||||
|
expectedDestPrefix: "192.0.2.1/32",
|
||||||
|
expectedInterface: expectedExtInt,
|
||||||
|
dialer: nbnet.NewDialer(),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "To duplicate internal route with custom dialer via physical interface",
|
||||||
|
destination: "10.0.0.2:53",
|
||||||
|
expectedDestPrefix: "10.0.0.2/32",
|
||||||
|
expectedInterface: expectedExtInt,
|
||||||
|
dialer: nbnet.NewDialer(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To duplicate internal route without custom dialer via physical interface", // local route takes precedence
|
||||||
|
destination: "10.0.0.2:53",
|
||||||
|
expectedSourceIP: "10.0.0.1",
|
||||||
|
expectedDestPrefix: "10.0.0.0/8",
|
||||||
|
expectedNextHop: "0.0.0.0",
|
||||||
|
expectedInterface: "Loopback Pseudo-Interface 1",
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "To unique vpn route with custom dialer via physical interface",
|
||||||
|
destination: "172.16.0.2:53",
|
||||||
|
expectedDestPrefix: "172.16.0.2/32",
|
||||||
|
expectedInterface: expectedExtInt,
|
||||||
|
dialer: nbnet.NewDialer(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To unique vpn route without custom dialer via vpn",
|
||||||
|
destination: "172.16.0.2:53",
|
||||||
|
expectedSourceIP: "100.64.0.1",
|
||||||
|
expectedDestPrefix: "172.16.0.0/12",
|
||||||
|
expectedNextHop: "0.0.0.0",
|
||||||
|
expectedInterface: "wgtest0",
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "To more specific route without custom dialer via vpn interface",
|
||||||
|
destination: "10.10.0.2:53",
|
||||||
|
expectedSourceIP: "100.64.0.1",
|
||||||
|
expectedDestPrefix: "10.10.0.0/24",
|
||||||
|
expectedNextHop: "0.0.0.0",
|
||||||
|
expectedInterface: "wgtest0",
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "To more specific route (local) without custom dialer via physical interface",
|
||||||
|
destination: "127.0.10.2:53",
|
||||||
|
expectedSourceIP: "10.0.0.1",
|
||||||
|
expectedDestPrefix: "127.0.0.0/8",
|
||||||
|
expectedNextHop: "0.0.0.0",
|
||||||
|
expectedInterface: "Loopback Pseudo-Interface 1",
|
||||||
|
dialer: &net.Dialer{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouting(t *testing.T) {
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
setupTestEnv(t)
|
||||||
|
|
||||||
|
route, err := fetchOriginalGateway()
|
||||||
|
require.NoError(t, err, "Failed to fetch original gateway")
|
||||||
|
ip, err := fetchInterfaceIP(route.InterfaceAlias)
|
||||||
|
require.NoError(t, err, "Failed to fetch interface IP")
|
||||||
|
|
||||||
|
output := testRoute(t, tc.destination, tc.dialer)
|
||||||
|
if tc.expectedInterface == expectedExtInt {
|
||||||
|
verifyOutput(t, output, ip, tc.expectedDestPrefix, route.NextHop, route.InterfaceAlias)
|
||||||
|
} else {
|
||||||
|
verifyOutput(t, output, tc.expectedSourceIP, tc.expectedDestPrefix, tc.expectedNextHop, tc.expectedInterface)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchInterfaceIP fetches the IPv4 address of the specified interface.
|
||||||
|
func fetchInterfaceIP(interfaceAlias string) (string, error) {
|
||||||
|
script := fmt.Sprintf(`Get-NetIPAddress -InterfaceAlias "%s" | Where-Object AddressFamily -eq 2 | Select-Object -ExpandProperty IPAddress`, interfaceAlias)
|
||||||
|
out, err := exec.Command("powershell", "-Command", script).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to execute Get-NetIPAddress: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := strings.TrimSpace(string(out))
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoute(t *testing.T, destination string, dialer dialer) *FindNetRouteOutput {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := dialer.DialContext(ctx, "udp", destination)
|
||||||
|
require.NoError(t, err, "Failed to dial destination")
|
||||||
|
defer func() {
|
||||||
|
err := conn.Close()
|
||||||
|
assert.NoError(t, err, "Failed to close connection")
|
||||||
|
}()
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(destination)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
script := fmt.Sprintf(`Find-NetRoute -RemoteIPAddress "%s" | Select-Object -Property IPAddress, InterfaceIndex, InterfaceAlias, AddressFamily, NextHop, DestinationPrefix | ConvertTo-Json`, host)
|
||||||
|
|
||||||
|
out, err := exec.Command("powershell", "-Command", script).Output()
|
||||||
|
require.NoError(t, err, "Failed to execute Find-NetRoute")
|
||||||
|
|
||||||
|
var outputs []FindNetRouteOutput
|
||||||
|
err = json.Unmarshal(out, &outputs)
|
||||||
|
require.NoError(t, err, "Failed to parse JSON outputs from Find-NetRoute")
|
||||||
|
|
||||||
|
require.Greater(t, len(outputs), 0, "No route found for destination")
|
||||||
|
combinedOutput := combineOutputs(outputs)
|
||||||
|
|
||||||
|
return combinedOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAndSetupDummyInterface(t *testing.T, interfaceName, ipAddressCIDR string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ip, ipNet, err := net.ParseCIDR(ipAddressCIDR)
|
||||||
|
require.NoError(t, err)
|
||||||
|
subnetMaskSize, _ := ipNet.Mask.Size()
|
||||||
|
script := fmt.Sprintf(`New-NetIPAddress -InterfaceAlias "%s" -IPAddress "%s" -PrefixLength %d -PolicyStore ActiveStore -Confirm:$False`, interfaceName, ip.String(), subnetMaskSize)
|
||||||
|
_, err = exec.Command("powershell", "-Command", script).CombinedOutput()
|
||||||
|
require.NoError(t, err, "Failed to assign IP address to loopback adapter")
|
||||||
|
|
||||||
|
// Wait for the IP address to be applied
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err = waitForIPAddress(ctx, interfaceName, ip.String())
|
||||||
|
require.NoError(t, err, "IP address not applied within timeout")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
script = fmt.Sprintf(`Remove-NetIPAddress -InterfaceAlias "%s" -IPAddress "%s" -Confirm:$False`, interfaceName, ip.String())
|
||||||
|
_, err = exec.Command("powershell", "-Command", script).CombinedOutput()
|
||||||
|
require.NoError(t, err, "Failed to remove IP address from loopback adapter")
|
||||||
|
})
|
||||||
|
|
||||||
|
return interfaceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchOriginalGateway() (*RouteInfo, error) {
|
||||||
|
cmd := exec.Command("powershell", "-Command", "Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Select-Object NextHop, RouteMetric, InterfaceAlias | ConvertTo-Json")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute Get-NetRoute: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeInfo RouteInfo
|
||||||
|
err = json.Unmarshal(output, &routeInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse JSON output: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &routeInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyOutput(t *testing.T, output *FindNetRouteOutput, sourceIP, destPrefix, nextHop, intf string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, sourceIP, output.IPAddress, "Source IP mismatch")
|
||||||
|
assert.Equal(t, destPrefix, output.DestinationPrefix, "Destination prefix mismatch")
|
||||||
|
assert.Equal(t, nextHop, output.NextHop, "Next hop mismatch")
|
||||||
|
assert.Equal(t, intf, output.InterfaceAlias, "Interface mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForIPAddress(ctx context.Context, interfaceAlias, expectedIPAddress string) error {
|
||||||
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ticker.C:
|
||||||
|
out, err := exec.Command("powershell", "-Command", fmt.Sprintf(`Get-NetIPAddress -InterfaceAlias "%s" | Select-Object -ExpandProperty IPAddress`, interfaceAlias)).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddresses := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||||
|
for _, ip := range ipAddresses {
|
||||||
|
if strings.TrimSpace(ip) == expectedIPAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func combineOutputs(outputs []FindNetRouteOutput) *FindNetRouteOutput {
|
||||||
|
var combined FindNetRouteOutput
|
||||||
|
|
||||||
|
for _, output := range outputs {
|
||||||
|
if output.IPAddress != "" {
|
||||||
|
combined.IPAddress = output.IPAddress
|
||||||
|
}
|
||||||
|
if output.InterfaceIndex != 0 {
|
||||||
|
combined.InterfaceIndex = output.InterfaceIndex
|
||||||
|
}
|
||||||
|
if output.InterfaceAlias != "" {
|
||||||
|
combined.InterfaceAlias = output.InterfaceAlias
|
||||||
|
}
|
||||||
|
if output.AddressFamily != 0 {
|
||||||
|
combined.AddressFamily = output.AddressFamily
|
||||||
|
}
|
||||||
|
if output.NextHop != "" {
|
||||||
|
combined.NextHop = output.NextHop
|
||||||
|
}
|
||||||
|
if output.DestinationPrefix != "" {
|
||||||
|
combined.DestinationPrefix = output.DestinationPrefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &combined
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDummyInterfacesAndRoutes(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
createAndSetupDummyInterface(t, "Loopback Pseudo-Interface 1", "10.0.0.1/8")
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package wgproxy
|
package wgproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,7 +23,7 @@ func (pl portLookup) searchFreePort() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pl portLookup) tryToBind(port int) error {
|
func (pl portLookup) tryToBind(port int) error {
|
||||||
l, err := nbnet.NewListener().ListenPacket(context.Background(), "udp", fmt.Sprintf(":%d", port))
|
l, err := net.ListenPacket("udp", fmt.Sprintf(":%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/pion/transport/v3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/ebpf"
|
"github.com/netbirdio/netbird/client/internal/ebpf"
|
||||||
@@ -29,7 +30,7 @@ type WGEBPFProxy struct {
|
|||||||
turnConnMutex sync.Mutex
|
turnConnMutex sync.Mutex
|
||||||
|
|
||||||
rawConn net.PacketConn
|
rawConn net.PacketConn
|
||||||
conn *net.UDPConn
|
conn transport.UDPConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWGEBPFProxy create new WGEBPFProxy instance
|
// NewWGEBPFProxy create new WGEBPFProxy instance
|
||||||
@@ -67,7 +68,7 @@ func (p *WGEBPFProxy) Listen() error {
|
|||||||
IP: net.ParseIP("127.0.0.1"),
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
p.conn, err = nbnet.ListenUDP("udp", &addr)
|
conn, err := nbnet.ListenUDP("udp", &addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cErr := p.Free()
|
cErr := p.Free()
|
||||||
if cErr != nil {
|
if cErr != nil {
|
||||||
@@ -75,6 +76,7 @@ func (p *WGEBPFProxy) Listen() error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.conn = conn
|
||||||
|
|
||||||
go p.proxyToRemote()
|
go p.proxyToRemote()
|
||||||
log.Infof("local wg proxy listening on: %d", wgPorxyPort)
|
log.Infof("local wg proxy listening on: %d", wgPorxyPort)
|
||||||
@@ -228,7 +230,7 @@ func (p *WGEBPFProxy) prepareSenderRawSocket() (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the fwmark on the socket.
|
// Set the fwmark on the socket.
|
||||||
err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, nbnet.NetbirdFwmark)
|
err = nbnet.SetSocketOpt(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("setting fwmark failed: %w", err)
|
return nil, fmt.Errorf("setting fwmark failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,17 +44,18 @@ type LoginRequest struct {
|
|||||||
// cleanNATExternalIPs clean map list of external IPs.
|
// cleanNATExternalIPs clean map list of external IPs.
|
||||||
// This is needed because the generated code
|
// This is needed because the generated code
|
||||||
// omits initialized empty slices due to omitempty tags
|
// omits initialized empty slices due to omitempty tags
|
||||||
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
|
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
|
||||||
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
|
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
|
||||||
IsLinuxDesktopClient bool `protobuf:"varint,8,opt,name=isLinuxDesktopClient,proto3" json:"isLinuxDesktopClient,omitempty"`
|
IsLinuxDesktopClient bool `protobuf:"varint,8,opt,name=isLinuxDesktopClient,proto3" json:"isLinuxDesktopClient,omitempty"`
|
||||||
Hostname string `protobuf:"bytes,9,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
Hostname string `protobuf:"bytes,9,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||||
RosenpassEnabled *bool `protobuf:"varint,10,opt,name=rosenpassEnabled,proto3,oneof" json:"rosenpassEnabled,omitempty"`
|
RosenpassEnabled *bool `protobuf:"varint,10,opt,name=rosenpassEnabled,proto3,oneof" json:"rosenpassEnabled,omitempty"`
|
||||||
InterfaceName *string `protobuf:"bytes,11,opt,name=interfaceName,proto3,oneof" json:"interfaceName,omitempty"`
|
InterfaceName *string `protobuf:"bytes,11,opt,name=interfaceName,proto3,oneof" json:"interfaceName,omitempty"`
|
||||||
WireguardPort *int64 `protobuf:"varint,12,opt,name=wireguardPort,proto3,oneof" json:"wireguardPort,omitempty"`
|
WireguardPort *int64 `protobuf:"varint,12,opt,name=wireguardPort,proto3,oneof" json:"wireguardPort,omitempty"`
|
||||||
OptionalPreSharedKey *string `protobuf:"bytes,13,opt,name=optionalPreSharedKey,proto3,oneof" json:"optionalPreSharedKey,omitempty"`
|
OptionalPreSharedKey *string `protobuf:"bytes,13,opt,name=optionalPreSharedKey,proto3,oneof" json:"optionalPreSharedKey,omitempty"`
|
||||||
DisableAutoConnect *bool `protobuf:"varint,14,opt,name=disableAutoConnect,proto3,oneof" json:"disableAutoConnect,omitempty"`
|
DisableAutoConnect *bool `protobuf:"varint,14,opt,name=disableAutoConnect,proto3,oneof" json:"disableAutoConnect,omitempty"`
|
||||||
ServerSSHAllowed *bool `protobuf:"varint,15,opt,name=serverSSHAllowed,proto3,oneof" json:"serverSSHAllowed,omitempty"`
|
ServerSSHAllowed *bool `protobuf:"varint,15,opt,name=serverSSHAllowed,proto3,oneof" json:"serverSSHAllowed,omitempty"`
|
||||||
RosenpassPermissive *bool `protobuf:"varint,16,opt,name=rosenpassPermissive,proto3,oneof" json:"rosenpassPermissive,omitempty"`
|
RosenpassPermissive *bool `protobuf:"varint,16,opt,name=rosenpassPermissive,proto3,oneof" json:"rosenpassPermissive,omitempty"`
|
||||||
|
ExtraIFaceBlacklist []string `protobuf:"bytes,17,rep,name=extraIFaceBlacklist,proto3" json:"extraIFaceBlacklist,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LoginRequest) Reset() {
|
func (x *LoginRequest) Reset() {
|
||||||
@@ -202,6 +203,13 @@ func (x *LoginRequest) GetRosenpassPermissive() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetExtraIFaceBlacklist() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ExtraIFaceBlacklist
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -1385,7 +1393,7 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74,
|
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74,
|
||||||
0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdd, 0x06, 0x0a, 0x0c, 0x4c, 0x6f,
|
0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8f, 0x07, 0x0a, 0x0c, 0x4c, 0x6f,
|
||||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
||||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
||||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
||||||
@@ -1430,192 +1438,195 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
|
0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
|
||||||
0x69, 0x76, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x48, 0x06, 0x52, 0x13, 0x72, 0x6f, 0x73,
|
0x69, 0x76, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x48, 0x06, 0x52, 0x13, 0x72, 0x6f, 0x73,
|
||||||
0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65,
|
0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65,
|
||||||
0x88, 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
|
0x88, 0x01, 0x01, 0x12, 0x30, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x72, 0x61, 0x49, 0x46, 0x61, 0x63,
|
||||||
0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x69, 0x6e, 0x74,
|
0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09,
|
||||||
0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x77,
|
0x52, 0x13, 0x65, 0x78, 0x74, 0x72, 0x61, 0x49, 0x46, 0x61, 0x63, 0x65, 0x42, 0x6c, 0x61, 0x63,
|
||||||
0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x42, 0x17, 0x0a, 0x15,
|
0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70,
|
||||||
0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72,
|
0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x69,
|
||||||
0x65, 0x64, 0x4b, 0x65, 0x79, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c,
|
0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x10, 0x0a, 0x0e,
|
||||||
0x65, 0x41, 0x75, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x42, 0x13, 0x0a, 0x11,
|
0x5f, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x42, 0x17,
|
||||||
0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x53, 0x48, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65,
|
0x0a, 0x15, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x72, 0x65, 0x53, 0x68,
|
||||||
0x64, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50,
|
0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x64, 0x69, 0x73, 0x61,
|
||||||
0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f,
|
0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x42, 0x13,
|
||||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e,
|
0x0a, 0x11, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x53, 0x48, 0x41, 0x6c, 0x6c, 0x6f,
|
||||||
0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01,
|
0x77, 0x65, 0x64, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
|
||||||
0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69,
|
0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x0d,
|
||||||
0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20,
|
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a,
|
0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01,
|
||||||
0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49,
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
|
0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12,
|
||||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
|
0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
|
||||||
0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
|
0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
|
||||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
|
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72,
|
||||||
0x65, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69,
|
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70,
|
||||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72,
|
0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69,
|
||||||
0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72,
|
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c,
|
||||||
0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
|
0x65, 0x74, 0x65, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
|
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
||||||
0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65,
|
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71,
|
0x6d, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50,
|
0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70,
|
||||||
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73,
|
||||||
0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
|
0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a,
|
0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74,
|
||||||
0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x61, 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75,
|
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||||
0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69,
|
0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20,
|
||||||
0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c,
|
||||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52,
|
0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47,
|
0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77,
|
||||||
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e,
|
||||||
0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43,
|
||||||
0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a,
|
||||||
0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66,
|
0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
|
0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66,
|
||||||
0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79,
|
0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f,
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65,
|
0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46,
|
||||||
0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69,
|
||||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b,
|
||||||
0x22, 0xce, 0x05, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e,
|
0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
||||||
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55,
|
||||||
0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55,
|
||||||
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
0x52, 0x4c, 0x22, 0xce, 0x05, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e,
|
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e,
|
||||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f,
|
||||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f,
|
0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e,
|
||||||
0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01,
|
||||||
0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65,
|
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10,
|
||||||
0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||||
0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64,
|
0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||||
0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x08, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69,
|
||||||
0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
|
0x72, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65,
|
||||||
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
0x63, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61,
|
||||||
|
0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69,
|
||||||
|
0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f,
|
||||||
|
0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79,
|
||||||
|
0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
||||||
0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63,
|
0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||||
0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12,
|
0x66, 0x71, 0x64, 0x6e, 0x12, 0x3c, 0x0a, 0x19, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65,
|
||||||
0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71,
|
0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,
|
||||||
0x64, 0x6e, 0x12, 0x3c, 0x0a, 0x19, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61,
|
0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63,
|
||||||
0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
|
0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,
|
||||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43,
|
0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43,
|
||||||
0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||||
0x12, 0x3e, 0x0a, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e,
|
0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63,
|
||||||
0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0b,
|
0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43,
|
0x6e, 0x74, 0x12, 0x52, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75,
|
||||||
0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
0x61, 0x72, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x0c, 0x20, 0x01,
|
||||||
0x12, 0x52, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72,
|
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x64, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b,
|
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x16,
|
||||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x6c, 0x61, 0x73, 0x74, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x48, 0x61, 0x6e,
|
||||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x16, 0x6c, 0x61,
|
0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52,
|
||||||
0x73, 0x74, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x73,
|
0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78,
|
||||||
0x68, 0x61, 0x6b, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x18,
|
0x12, 0x18, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28,
|
||||||
0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x12, 0x18,
|
0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f,
|
||||||
0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52,
|
0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f,
|
||||||
0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65,
|
|
||||||
0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01,
|
|
||||||
0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61,
|
|
||||||
0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x10,
|
|
||||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x07,
|
|
||||||
0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e,
|
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
|
||||||
0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63,
|
|
||||||
0x79, 0x22, 0xec, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53,
|
|
||||||
0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
|
||||||
0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02,
|
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f,
|
|
||||||
0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18,
|
|
||||||
0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74,
|
|
||||||
0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04,
|
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f,
|
|
||||||
0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05,
|
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45,
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45,
|
||||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70,
|
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
||||||
0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20,
|
0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33,
|
||||||
0x01, 0x28, 0x08, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65,
|
0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74,
|
0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65,
|
||||||
0x22, 0x53, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
0x6e, 0x63, 0x79, 0x22, 0xec, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||||
0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02,
|
0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79,
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28,
|
||||||
0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63,
|
||||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x57, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49,
|
||||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18,
|
0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x2a, 0x0a, 0x10,
|
||||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63,
|
0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||||
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
|
||||||
0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x52,
|
0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65,
|
||||||
0x0a, 0x0a, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x18,
|
||||||
0x55, 0x52, 0x49, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x49, 0x12, 0x1c,
|
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73,
|
||||||
0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f,
|
||||||
0x08, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
0x75, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74,
|
||||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72,
|
0x65, 0x73, 0x22, 0x53, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x6f, 0x72, 0x22, 0x72, 0x0a, 0x0c, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61,
|
0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||||
0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20,
|
0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64,
|
||||||
0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
|
||||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64,
|
0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x57, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
|
||||||
0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
|
||||||
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xd2, 0x02, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53,
|
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72,
|
||||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
|
0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x22, 0x52, 0x0a, 0x0a, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10,
|
||||||
0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x0a, 0x03, 0x55, 0x52, 0x49, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x49,
|
||||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e,
|
0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20,
|
||||||
0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
|
0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14,
|
||||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
|
0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
|
||||||
0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
0x72, 0x72, 0x6f, 0x72, 0x22, 0x72, 0x0a, 0x0c, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53,
|
||||||
0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18,
|
||||||
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18,
|
||||||
0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
|
0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
|
||||||
0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62,
|
||||||
0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11,
|
0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61,
|
0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xd2, 0x02, 0x0a, 0x0a, 0x46, 0x75, 0x6c,
|
||||||
0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x6e, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||||
0x6c, 0x61, 0x79, 0x73, 0x12, 0x35, 0x0a, 0x0b, 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76,
|
0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x6f, 0x6e, 0x2e, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69,
|
||||||
0x0a, 0x64, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d,
|
0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a,
|
0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53,
|
||||||
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64,
|
0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f,
|
0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57,
|
0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
|
||||||
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74,
|
0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74,
|
||||||
0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x72, 0x65,
|
||||||
0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x6c, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65,
|
||||||
0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61,
|
0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x12, 0x35, 0x0a, 0x0b, 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x65,
|
||||||
0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61,
|
0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x61,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
0x65, 0x52, 0x0a, 0x64, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02,
|
||||||
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04,
|
0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
||||||
0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15,
|
||||||
0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73,
|
||||||
0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71,
|
||||||
0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65,
|
||||||
|
0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
|
||||||
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e,
|
||||||
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74,
|
||||||
|
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33,
|
||||||
|
0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
|
0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61,
|
||||||
|
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
|
0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
||||||
|
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65,
|
||||||
|
0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ message LoginRequest {
|
|||||||
optional bool serverSSHAllowed = 15;
|
optional bool serverSSHAllowed = 15;
|
||||||
|
|
||||||
optional bool rosenpassPermissive = 16;
|
optional bool rosenpassPermissive = 16;
|
||||||
|
|
||||||
|
repeated string extraIFaceBlacklist = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ func (s *Server) Start() error {
|
|||||||
// mechanism to keep the client connected even when the connection is lost.
|
// 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.
|
// 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,
|
func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Config, statusRecorder *peer.Status,
|
||||||
mgmProbe *internal.Probe, signalProbe *internal.Probe, relayProbe *internal.Probe, wgProbe *internal.Probe) {
|
mgmProbe *internal.Probe, signalProbe *internal.Probe, relayProbe *internal.Probe, wgProbe *internal.Probe,
|
||||||
|
) {
|
||||||
backOff := getConnectWithBackoff(ctx)
|
backOff := getConnectWithBackoff(ctx)
|
||||||
retryStarted := false
|
retryStarted := false
|
||||||
|
|
||||||
@@ -351,6 +352,11 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
s.latestConfigInput.WireguardPort = &port
|
s.latestConfigInput.WireguardPort = &port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(msg.ExtraIFaceBlacklist) > 0 {
|
||||||
|
inputConfig.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
||||||
|
s.latestConfigInput.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
||||||
|
}
|
||||||
|
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if msg.OptionalPreSharedKey != nil {
|
if msg.OptionalPreSharedKey != nil {
|
||||||
@@ -712,7 +718,7 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
|||||||
BytesRx: peerState.BytesRx,
|
BytesRx: peerState.BytesRx,
|
||||||
BytesTx: peerState.BytesTx,
|
BytesTx: peerState.BytesTx,
|
||||||
RosenpassEnabled: peerState.RosenpassEnabled,
|
RosenpassEnabled: peerState.RosenpassEnabled,
|
||||||
Routes: maps.Keys(peerState.Routes),
|
Routes: maps.Keys(peerState.GetRoutes()),
|
||||||
Latency: durationpb.New(peerState.Latency),
|
Latency: durationpb.New(peerState.Latency),
|
||||||
}
|
}
|
||||||
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -114,7 +115,8 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false)
|
ia, _ := integrations.NewIntegratedValidator(eventStore)
|
||||||
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ func Detect(ctx context.Context) string {
|
|||||||
detectDigitalOcean,
|
detectDigitalOcean,
|
||||||
detectGCP,
|
detectGCP,
|
||||||
detectOracle,
|
detectOracle,
|
||||||
detectIBMCloud,
|
|
||||||
detectSoftlayer,
|
|
||||||
detectVultr,
|
detectVultr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func detectGCP(ctx context.Context) string {
|
func detectGCP(ctx context.Context) string {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", "http://metadata.google.internal", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://169.254.169.254", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package detect_cloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func detectIBMCloud(ctx context.Context) string {
|
|
||||||
v1ResultChan := make(chan bool, 1)
|
|
||||||
v2ResultChan := make(chan bool, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
v1ResultChan <- detectIBMSecure(ctx)
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
v2ResultChan <- detectIBM(ctx)
|
|
||||||
}()
|
|
||||||
|
|
||||||
v1Result, v2Result := <-v1ResultChan, <-v2ResultChan
|
|
||||||
|
|
||||||
if v1Result || v2Result {
|
|
||||||
return "IBM Cloud"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectIBMSecure(ctx context.Context) bool {
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "PUT", "https://api.metadata.cloud.ibm.com/instance_identity/v1/token", nil)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
return resp.StatusCode == http.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectIBM(ctx context.Context) bool {
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "PUT", "http://api.metadata.cloud.ibm.com/instance_identity/v1/token", nil)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
return resp.StatusCode == http.StatusOK
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package detect_cloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func detectSoftlayer(ctx context.Context) string {
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.service.softlayer.com/rest/v3/SoftLayer_Resource_Metadata/UserMetadata.txt", nil)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
// Since SoftLayer was acquired by IBM, we should return "IBM Cloud"
|
|
||||||
return "IBM Cloud"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
6
go.mod
6
go.mod
@@ -46,21 +46,21 @@ require (
|
|||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/go-cmp v0.5.9
|
github.com/google/go-cmp v0.5.9
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
|
github.com/google/martian/v3 v3.0.0
|
||||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||||
github.com/gopacket/gopacket v1.1.1
|
github.com/gopacket/gopacket v1.1.1
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/libp2p/go-netroute v0.2.0
|
github.com/libp2p/go-netroute v0.2.1
|
||||||
github.com/magiconair/properties v1.8.5
|
github.com/magiconair/properties v1.8.5
|
||||||
github.com/mattn/go-sqlite3 v1.14.19
|
github.com/mattn/go-sqlite3 v1.14.19
|
||||||
github.com/mdlayher/socket v0.4.1
|
github.com/mdlayher/socket v0.4.1
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/nadoo/ipset v0.5.0
|
github.com/nadoo/ipset v0.5.0
|
||||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20240415094251-369eb33c9b01
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552
|
|
||||||
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -255,6 +255,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A=
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A=
|
||||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
|
||||||
@@ -344,8 +345,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/libp2p/go-netroute v0.2.0 h1:0FpsbsvuSnAhXFnCY0VLFbJOzaK0VnP0r1QT/o4nWRE=
|
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
||||||
github.com/libp2p/go-netroute v0.2.0/go.mod h1:Vio7LTzZ+6hoT4CMZi5/6CpY3Snzh2vgZhWgxMNwlQI=
|
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
|
||||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
@@ -382,10 +383,8 @@ github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
|
|||||||
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
||||||
github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e h1:PURA50S8u4mF6RrkYYCAvvPCixhqqEiEy3Ej6avh04c=
|
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/ice/v3 v3.0.0-20240315174635-e72a50fcb64e/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q=
|
||||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552 h1:yzcQKizAK9YufCHMMCIsr467Dw/OU/4xyHbWizGb1E4=
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20240415094251-369eb33c9b01 h1:Fu9fq0ndfKVuFTEwbc8Etqui10BOkcMTv0UqcMy0RuY=
|
||||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA=
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20240415094251-369eb33c9b01/go.mod h1:kxks50DrZnhW+oRTdHOkVOJbcTcyo766am8RBugo+Yc=
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552 h1:OFlzVZtkXCoJsfDKrMigFpuad8ZXTm8epq6x27K0irA=
|
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM=
|
|
||||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
|
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
|
||||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=
|
github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=
|
||||||
@@ -660,7 +659,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@@ -747,7 +745,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl"
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type wgKernelConfigurer struct {
|
type wgKernelConfigurer struct {
|
||||||
@@ -31,7 +29,7 @@ func (c *wgKernelConfigurer) configureInterface(privateKey string, port int) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fwmark := nbnet.NetbirdFwmark
|
fwmark := getFwmark()
|
||||||
config := wgtypes.Config{
|
config := wgtypes.Config{
|
||||||
PrivateKey: &key,
|
PrivateKey: &key,
|
||||||
ReplacePeers: true,
|
ReplacePeers: true,
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ func toWgUserspaceString(wgCfg wgtypes.Config) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getFwmark() int {
|
func getFwmark() int {
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" && !nbnet.CustomRoutingDisabled() {
|
||||||
return nbnet.NetbirdFwmark
|
return nbnet.NetbirdFwmark
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ services:
|
|||||||
command: [
|
command: [
|
||||||
"--port", "443",
|
"--port", "443",
|
||||||
"--log-file", "console",
|
"--log-file", "console",
|
||||||
|
"--log-level", "info",
|
||||||
"--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS",
|
"--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS",
|
||||||
"--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN",
|
"--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN",
|
||||||
"--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"
|
"--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: "3"
|
|||||||
services:
|
services:
|
||||||
#UI dashboard
|
#UI dashboard
|
||||||
dashboard:
|
dashboard:
|
||||||
image: wiretrustee/dashboard:$NETBIRD_DASHBOARD_TAG
|
image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
#ports:
|
#ports:
|
||||||
# - 80:80
|
# - 80:80
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
mgmt "github.com/netbirdio/netbird/management/server"
|
mgmt "github.com/netbirdio/netbird/management/server"
|
||||||
@@ -30,6 +32,12 @@ import (
|
|||||||
|
|
||||||
const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
_ = util.InitLog("debug", "console")
|
||||||
|
code := m.Run()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
level, _ := log.ParseLevel("debug")
|
level, _ := log.ParseLevel("debug")
|
||||||
@@ -60,7 +68,8 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
|||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false)
|
ia, _ := integrations.NewIntegratedValidator(eventStore)
|
||||||
|
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
@@ -172,8 +173,12 @@ var (
|
|||||||
log.Infof("geo location service has been initialized from %s", config.Datadir)
|
log.Infof("geo location service has been initialized from %s", config.Datadir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
integratedPeerValidator, err := integrations.NewIntegratedValidator(eventStore)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize integrated peer validator: %v", err)
|
||||||
|
}
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||||
dnsDomain, eventStore, geo, userDeleteFromIDPEnabled)
|
dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to build default manager: %v", err)
|
return fmt.Errorf("failed to build default manager: %v", err)
|
||||||
}
|
}
|
||||||
@@ -246,7 +251,7 @@ var (
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(cmd.Context())
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg)
|
httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg, integratedPeerValidator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
||||||
}
|
}
|
||||||
@@ -323,6 +328,7 @@ var (
|
|||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
<-stopCh
|
<-stopCh
|
||||||
|
integratedPeerValidator.Stop()
|
||||||
if geo != nil {
|
if geo != nil {
|
||||||
_ = geo.Stop()
|
_ = geo.Stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ import (
|
|||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/additions"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/base62"
|
"github.com/netbirdio/netbird/base62"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/account"
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
|
"github.com/netbirdio/netbird/management/server/integrated_validator"
|
||||||
|
"github.com/netbirdio/netbird/management/server/integration_reference"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/posture"
|
"github.com/netbirdio/netbird/management/server/posture"
|
||||||
@@ -85,12 +86,12 @@ type AccountManager interface {
|
|||||||
GetAllPATs(accountID string, initiatorUserID string, targetUserID string) ([]*PersonalAccessToken, error)
|
GetAllPATs(accountID string, initiatorUserID string, targetUserID string) ([]*PersonalAccessToken, error)
|
||||||
UpdatePeerSSHKey(peerID string, sshKey string) error
|
UpdatePeerSSHKey(peerID string, sshKey string) error
|
||||||
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
|
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
|
||||||
GetGroup(accountId, groupID, userID string) (*Group, error)
|
GetGroup(accountId, groupID, userID string) (*nbgroup.Group, error)
|
||||||
GetAllGroups(accountID, userID string) ([]*Group, error)
|
GetAllGroups(accountID, userID string) ([]*nbgroup.Group, error)
|
||||||
GetGroupByName(groupName, accountID string) (*Group, error)
|
GetGroupByName(groupName, accountID string) (*nbgroup.Group, error)
|
||||||
SaveGroup(accountID, userID string, group *Group) error
|
SaveGroup(accountID, userID string, group *nbgroup.Group) error
|
||||||
DeleteGroup(accountId, userId, groupID string) error
|
DeleteGroup(accountId, userId, groupID string) error
|
||||||
ListGroups(accountId string) ([]*Group, error)
|
ListGroups(accountId string) ([]*nbgroup.Group, error)
|
||||||
GroupAddPeer(accountId, groupID, peerID string) error
|
GroupAddPeer(accountId, groupID, peerID string) error
|
||||||
GroupDeletePeer(accountId, groupID, peerID string) error
|
GroupDeletePeer(accountId, groupID, peerID string) error
|
||||||
GetPolicy(accountID, policyID, userID string) (*Policy, error)
|
GetPolicy(accountID, policyID, userID string) (*Policy, error)
|
||||||
@@ -124,6 +125,9 @@ type AccountManager interface {
|
|||||||
DeletePostureChecks(accountID, postureChecksID, userID string) error
|
DeletePostureChecks(accountID, postureChecksID, userID string) error
|
||||||
ListPostureChecks(accountID, userID string) ([]*posture.Checks, error)
|
ListPostureChecks(accountID, userID string) ([]*posture.Checks, error)
|
||||||
GetIdpManager() idp.Manager
|
GetIdpManager() idp.Manager
|
||||||
|
UpdateIntegratedValidatorGroups(accountID string, userID string, groups []string) error
|
||||||
|
GroupValidation(accountId string, groups []string) (bool, error)
|
||||||
|
GetValidatedPeers(account *Account) (map[string]struct{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
@@ -152,6 +156,8 @@ type DefaultAccountManager struct {
|
|||||||
|
|
||||||
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
|
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
|
||||||
userDeleteFromIDPEnabled bool
|
userDeleteFromIDPEnabled bool
|
||||||
|
|
||||||
|
integratedPeerValidator integrated_validator.IntegratedValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings represents Account settings structure that can be modified via API and Dashboard
|
// Settings represents Account settings structure that can be modified via API and Dashboard
|
||||||
@@ -218,8 +224,8 @@ type Account struct {
|
|||||||
PeersG []nbpeer.Peer `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
PeersG []nbpeer.Peer `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Users map[string]*User `gorm:"-"`
|
Users map[string]*User `gorm:"-"`
|
||||||
UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Groups map[string]*Group `gorm:"-"`
|
Groups map[string]*nbgroup.Group `gorm:"-"`
|
||||||
GroupsG []Group `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
GroupsG []nbgroup.Group `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Policies []*Policy `gorm:"foreignKey:AccountID;references:id"`
|
Policies []*Policy `gorm:"foreignKey:AccountID;references:id"`
|
||||||
Routes map[string]*route.Route `gorm:"-"`
|
Routes map[string]*route.Route `gorm:"-"`
|
||||||
RoutesG []route.Route `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
RoutesG []route.Route `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
@@ -236,19 +242,19 @@ type UserPermissions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
AutoGroups []string `json:"auto_groups"`
|
AutoGroups []string `json:"auto_groups"`
|
||||||
Status string `json:"-"`
|
Status string `json:"-"`
|
||||||
IsServiceUser bool `json:"is_service_user"`
|
IsServiceUser bool `json:"is_service_user"`
|
||||||
IsBlocked bool `json:"is_blocked"`
|
IsBlocked bool `json:"is_blocked"`
|
||||||
NonDeletable bool `json:"non_deletable"`
|
NonDeletable bool `json:"non_deletable"`
|
||||||
LastLogin time.Time `json:"last_login"`
|
LastLogin time.Time `json:"last_login"`
|
||||||
Issued string `json:"issued"`
|
Issued string `json:"issued"`
|
||||||
IntegrationReference IntegrationReference `json:"-"`
|
IntegrationReference integration_reference.IntegrationReference `json:"-"`
|
||||||
Permissions UserPermissions `json:"permissions"`
|
Permissions UserPermissions `json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRoutesToSync returns the enabled routes for the peer ID and the routes
|
// getRoutesToSync returns the enabled routes for the peer ID and the routes
|
||||||
@@ -272,7 +278,7 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*rou
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterRoutesByHAMembership filters and returns a list of routes that don't share the same HA route membership
|
// filterRoutesFromPeersOfSameHAGroup filters and returns a list of routes that don't share the same HA route membership
|
||||||
func (a *Account) filterRoutesFromPeersOfSameHAGroup(routes []*route.Route, peerMemberships lookupMap) []*route.Route {
|
func (a *Account) filterRoutesFromPeersOfSameHAGroup(routes []*route.Route, peerMemberships lookupMap) []*route.Route {
|
||||||
var filteredRoutes []*route.Route
|
var filteredRoutes []*route.Route
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
@@ -372,25 +378,26 @@ func (a *Account) GetRoutesByPrefix(prefix netip.Prefix) []*route.Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetGroup returns a group by ID if exists, nil otherwise
|
// GetGroup returns a group by ID if exists, nil otherwise
|
||||||
func (a *Account) GetGroup(groupID string) *Group {
|
func (a *Account) GetGroup(groupID string) *nbgroup.Group {
|
||||||
return a.Groups[groupID]
|
return a.Groups[groupID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerNetworkMap returns a group by ID if exists, nil otherwise
|
// GetPeerNetworkMap returns a group by ID if exists, nil otherwise
|
||||||
func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
|
func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string, validatedPeersMap map[string]struct{}) *NetworkMap {
|
||||||
peer := a.Peers[peerID]
|
peer := a.Peers[peerID]
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
return &NetworkMap{
|
return &NetworkMap{
|
||||||
Network: a.Network.Copy(),
|
Network: a.Network.Copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer})
|
|
||||||
if len(validatedPeers) == 0 {
|
if _, ok := validatedPeersMap[peerID]; !ok {
|
||||||
return &NetworkMap{
|
return &NetworkMap{
|
||||||
Network: a.Network.Copy(),
|
Network: a.Network.Copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aclPeers, firewallRules := a.getPeerConnectionResources(peerID)
|
|
||||||
|
aclPeers, firewallRules := a.getPeerConnectionResources(peerID, validatedPeersMap)
|
||||||
// exclude expired peers
|
// exclude expired peers
|
||||||
var peersToConnect []*nbpeer.Peer
|
var peersToConnect []*nbpeer.Peer
|
||||||
var expiredPeers []*nbpeer.Peer
|
var expiredPeers []*nbpeer.Peer
|
||||||
@@ -564,7 +571,7 @@ func (a *Account) FindUser(userID string) (*User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindGroupByName looks for a given group in the Account by name or returns error if the group wasn't found.
|
// FindGroupByName looks for a given group in the Account by name or returns error if the group wasn't found.
|
||||||
func (a *Account) FindGroupByName(groupName string) (*Group, error) {
|
func (a *Account) FindGroupByName(groupName string) (*nbgroup.Group, error) {
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.Groups {
|
||||||
if group.Name == groupName {
|
if group.Name == groupName {
|
||||||
return group, nil
|
return group, nil
|
||||||
@@ -583,6 +590,20 @@ func (a *Account) FindSetupKey(setupKey string) (*SetupKey, error) {
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerGroupsList return with the list of groups ID.
|
||||||
|
func (a *Account) GetPeerGroupsList(peerID string) []string {
|
||||||
|
var grps []string
|
||||||
|
for groupID, group := range a.Groups {
|
||||||
|
for _, id := range group.Peers {
|
||||||
|
if id == peerID {
|
||||||
|
grps = append(grps, groupID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return grps
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Account) getUserGroups(userID string) ([]string, error) {
|
func (a *Account) getUserGroups(userID string) ([]string, error) {
|
||||||
user, err := a.FindUser(userID)
|
user, err := a.FindUser(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -660,7 +681,7 @@ func (a *Account) Copy() *Account {
|
|||||||
setupKeys[id] = key.Copy()
|
setupKeys[id] = key.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := map[string]*Group{}
|
groups := map[string]*nbgroup.Group{}
|
||||||
for id, group := range a.Groups {
|
for id, group := range a.Groups {
|
||||||
groups[id] = group.Copy()
|
groups[id] = group.Copy()
|
||||||
}
|
}
|
||||||
@@ -713,7 +734,7 @@ func (a *Account) Copy() *Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) GetGroupAll() (*Group, error) {
|
func (a *Account) GetGroupAll() (*nbgroup.Group, error) {
|
||||||
for _, g := range a.Groups {
|
for _, g := range a.Groups {
|
||||||
if g.Name == "All" {
|
if g.Name == "All" {
|
||||||
return g, nil
|
return g, nil
|
||||||
@@ -734,7 +755,7 @@ func (a *Account) SetJWTGroups(userID string, groupsNames []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
existedGroupsByName := make(map[string]*Group)
|
existedGroupsByName := make(map[string]*nbgroup.Group)
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.Groups {
|
||||||
existedGroupsByName[group.Name] = group
|
existedGroupsByName[group.Name] = group
|
||||||
}
|
}
|
||||||
@@ -743,7 +764,7 @@ func (a *Account) SetJWTGroups(userID string, groupsNames []string) bool {
|
|||||||
removed := 0
|
removed := 0
|
||||||
jwtAutoGroups := make(map[string]struct{})
|
jwtAutoGroups := make(map[string]struct{})
|
||||||
for i, id := range user.AutoGroups {
|
for i, id := range user.AutoGroups {
|
||||||
if group, ok := a.Groups[id]; ok && group.Issued == GroupIssuedJWT {
|
if group, ok := a.Groups[id]; ok && group.Issued == nbgroup.GroupIssuedJWT {
|
||||||
jwtAutoGroups[group.Name] = struct{}{}
|
jwtAutoGroups[group.Name] = struct{}{}
|
||||||
user.AutoGroups = append(user.AutoGroups[:i-removed], user.AutoGroups[i-removed+1:]...)
|
user.AutoGroups = append(user.AutoGroups[:i-removed], user.AutoGroups[i-removed+1:]...)
|
||||||
removed++
|
removed++
|
||||||
@@ -756,15 +777,15 @@ func (a *Account) SetJWTGroups(userID string, groupsNames []string) bool {
|
|||||||
for _, name := range groupsNames {
|
for _, name := range groupsNames {
|
||||||
group, ok := existedGroupsByName[name]
|
group, ok := existedGroupsByName[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
group = &Group{
|
group = &nbgroup.Group{
|
||||||
ID: xid.New().String(),
|
ID: xid.New().String(),
|
||||||
Name: name,
|
Name: name,
|
||||||
Issued: GroupIssuedJWT,
|
Issued: nbgroup.GroupIssuedJWT,
|
||||||
}
|
}
|
||||||
a.Groups[group.ID] = group
|
a.Groups[group.ID] = group
|
||||||
}
|
}
|
||||||
// only JWT groups will be synced
|
// only JWT groups will be synced
|
||||||
if group.Issued == GroupIssuedJWT {
|
if group.Issued == nbgroup.GroupIssuedJWT {
|
||||||
user.AutoGroups = append(user.AutoGroups, group.ID)
|
user.AutoGroups = append(user.AutoGroups, group.ID)
|
||||||
if _, ok := jwtAutoGroups[name]; !ok {
|
if _, ok := jwtAutoGroups[name]; !ok {
|
||||||
modified = true
|
modified = true
|
||||||
@@ -837,6 +858,7 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
|
|||||||
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
||||||
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store, geo *geolocation.Geolocation,
|
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store, geo *geolocation.Geolocation,
|
||||||
userDeleteFromIDPEnabled bool,
|
userDeleteFromIDPEnabled bool,
|
||||||
|
integratedPeerValidator integrated_validator.IntegratedValidator,
|
||||||
) (*DefaultAccountManager, error) {
|
) (*DefaultAccountManager, error) {
|
||||||
am := &DefaultAccountManager{
|
am := &DefaultAccountManager{
|
||||||
Store: store,
|
Store: store,
|
||||||
@@ -850,6 +872,7 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
|
|||||||
eventStore: eventStore,
|
eventStore: eventStore,
|
||||||
peerLoginExpiry: NewDefaultScheduler(),
|
peerLoginExpiry: NewDefaultScheduler(),
|
||||||
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
||||||
|
integratedPeerValidator: integratedPeerValidator,
|
||||||
}
|
}
|
||||||
allAccounts := store.GetAllAccounts()
|
allAccounts := store.GetAllAccounts()
|
||||||
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
||||||
@@ -906,6 +929,8 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
am.integratedPeerValidator.SetPeerInvalidationListener(am.onPeersInvalidated)
|
||||||
|
|
||||||
return am, nil
|
return am, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -948,7 +973,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
|
|||||||
return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account")
|
return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore)
|
err = am.integratedPeerValidator.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1095,7 +1120,7 @@ func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
|
|||||||
return status.Errorf(status.PermissionDenied, "user is not allowed to delete account")
|
return status.Errorf(status.PermissionDenied, "user is not allowed to delete account")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Id != account.CreatedBy {
|
if user.Role != UserRoleOwner {
|
||||||
return status.Errorf(status.PermissionDenied, "user is not allowed to delete account. Only account owner can delete account")
|
return status.Errorf(status.PermissionDenied, "user is not allowed to delete account. Only account owner can delete account")
|
||||||
}
|
}
|
||||||
for _, otherUser := range account.Users {
|
for _, otherUser := range account.Users {
|
||||||
@@ -1448,7 +1473,7 @@ func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims
|
|||||||
// if domain already has a primary account, add regular user
|
// if domain already has a primary account, add regular user
|
||||||
if domainAcc != nil {
|
if domainAcc != nil {
|
||||||
account = domainAcc
|
account = domainAcc
|
||||||
account.Users[claims.UserId] = NewRegularUser(claims.UserId)
|
account.Users[claims.UserId] = NewRegularUser(claims.UserId, account.Id)
|
||||||
err = am.Store.SaveAccount(account)
|
err = am.Store.SaveAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1823,18 +1848,29 @@ func (am *DefaultAccountManager) CheckUserAccessByJWTGroups(claims jwtclaims.Aut
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) onPeersInvalidated(accountID string) {
|
||||||
|
log.Debugf("validated peers has been invalidated for account %s", accountID)
|
||||||
|
updatedAccount, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get account %s: %v", accountID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
am.updateAccountPeers(updatedAccount)
|
||||||
|
}
|
||||||
|
|
||||||
// addAllGroup to account object if it doesn't exist
|
// addAllGroup to account object if it doesn't exist
|
||||||
func addAllGroup(account *Account) error {
|
func addAllGroup(account *Account) error {
|
||||||
if len(account.Groups) == 0 {
|
if len(account.Groups) == 0 {
|
||||||
allGroup := &Group{
|
allGroup := &nbgroup.Group{
|
||||||
ID: xid.New().String(),
|
ID: xid.New().String(),
|
||||||
Name: "All",
|
Name: "All",
|
||||||
Issued: GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
|
AccountID: account.Id,
|
||||||
}
|
}
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
allGroup.Peers = append(allGroup.Peers, peer.ID)
|
allGroup.Peers = append(allGroup.Peers, peer.ID)
|
||||||
}
|
}
|
||||||
account.Groups = map[string]*Group{allGroup.ID: allGroup}
|
account.Groups = map[string]*nbgroup.Group{allGroup.ID: allGroup}
|
||||||
|
|
||||||
id := xid.New().String()
|
id := xid.New().String()
|
||||||
|
|
||||||
@@ -1873,7 +1909,7 @@ func newAccountWithId(accountID, userID, domain string) *Account {
|
|||||||
routes := make(map[string]*route.Route)
|
routes := make(map[string]*route.Route)
|
||||||
setupKeys := map[string]*SetupKey{}
|
setupKeys := map[string]*SetupKey{}
|
||||||
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
||||||
users[userID] = NewOwnerUser(userID)
|
users[userID] = NewOwnerUser(userID, accountID)
|
||||||
dnsSettings := DNSSettings{
|
dnsSettings := DNSSettings{
|
||||||
DisabledManagementGroups: make([]string, 0),
|
DisabledManagementGroups: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ package account
|
|||||||
type ExtraSettings struct {
|
type ExtraSettings struct {
|
||||||
// PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator
|
// PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator
|
||||||
PeerApprovalEnabled bool
|
PeerApprovalEnabled bool
|
||||||
|
|
||||||
|
// IntegratedValidatorGroups list of group IDs to be used with integrated approval configurations
|
||||||
|
IntegratedValidatorGroups []string `gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy copies the ExtraSettings struct
|
// Copy copies the ExtraSettings struct
|
||||||
func (e *ExtraSettings) Copy() *ExtraSettings {
|
func (e *ExtraSettings) Copy() *ExtraSettings {
|
||||||
|
var cpGroup []string
|
||||||
|
|
||||||
return &ExtraSettings{
|
return &ExtraSettings{
|
||||||
PeerApprovalEnabled: e.PeerApprovalEnabled,
|
PeerApprovalEnabled: e.PeerApprovalEnabled,
|
||||||
|
IntegratedValidatorGroups: append(cpGroup, e.IntegratedValidatorGroups...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,20 +12,57 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
||||||
"github.com/netbirdio/netbird/management/server/posture"
|
|
||||||
"github.com/netbirdio/netbird/route"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/netbirdio/netbird/management/server/posture"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MocIntegratedValidator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MocIntegratedValidator) ValidateExtraSettings(newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MocIntegratedValidator) ValidatePeer(update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error) {
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
func (a MocIntegratedValidator) GetValidatedPeers(accountID string, groups map[string]*group.Group, peers map[string]*nbpeer.Peer, extraSettings *account.ExtraSettings) (map[string]struct{}, error) {
|
||||||
|
validatedPeers := make(map[string]struct{})
|
||||||
|
for _, peer := range peers {
|
||||||
|
validatedPeers[peer.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
return validatedPeers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer {
|
||||||
|
return peer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool) {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) PeerDeleted(_, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) SetPeerInvalidationListener(func(accountID string)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) Stop() {
|
||||||
|
}
|
||||||
|
|
||||||
func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) {
|
func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
peer := &nbpeer.Peer{
|
peer := &nbpeer.Peer{
|
||||||
@@ -367,7 +404,12 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
|
|||||||
account.Groups[all.ID].Peers = append(account.Groups[all.ID].Peers, peer.ID)
|
account.Groups[all.ID].Peers = append(account.Groups[all.ID].Peers, peer.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
networkMap := account.GetPeerNetworkMap(testCase.peerID, "netbird.io")
|
validatedPeers := map[string]struct{}{}
|
||||||
|
for p := range account.Peers {
|
||||||
|
validatedPeers[p] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
networkMap := account.GetPeerNetworkMap(testCase.peerID, "netbird.io", validatedPeers)
|
||||||
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
|
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
|
||||||
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
|
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
|
||||||
}
|
}
|
||||||
@@ -667,7 +709,7 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
|||||||
require.NoError(t, err, "get account by token failed")
|
require.NoError(t, err, "get account by token failed")
|
||||||
require.Len(t, account.Groups, 3, "groups should be added to the account")
|
require.Len(t, account.Groups, 3, "groups should be added to the account")
|
||||||
|
|
||||||
groupsByNames := map[string]*Group{}
|
groupsByNames := map[string]*group.Group{}
|
||||||
for _, g := range account.Groups {
|
for _, g := range account.Groups {
|
||||||
groupsByNames[g.Name] = g
|
groupsByNames[g.Name] = g
|
||||||
}
|
}
|
||||||
@@ -675,12 +717,12 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
|||||||
g1, ok := groupsByNames["group1"]
|
g1, ok := groupsByNames["group1"]
|
||||||
require.True(t, ok, "group1 should be added to the account")
|
require.True(t, ok, "group1 should be added to the account")
|
||||||
require.Equal(t, g1.Name, "group1", "group1 name should match")
|
require.Equal(t, g1.Name, "group1", "group1 name should match")
|
||||||
require.Equal(t, g1.Issued, GroupIssuedJWT, "group1 issued should match")
|
require.Equal(t, g1.Issued, group.GroupIssuedJWT, "group1 issued should match")
|
||||||
|
|
||||||
g2, ok := groupsByNames["group2"]
|
g2, ok := groupsByNames["group2"]
|
||||||
require.True(t, ok, "group2 should be added to the account")
|
require.True(t, ok, "group2 should be added to the account")
|
||||||
require.Equal(t, g2.Name, "group2", "group2 name should match")
|
require.Equal(t, g2.Name, "group2", "group2 name should match")
|
||||||
require.Equal(t, g2.Issued, GroupIssuedJWT, "group2 issued should match")
|
require.Equal(t, g2.Issued, group.GroupIssuedJWT, "group2 issued should match")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,7 +842,7 @@ func TestAccountManager_SetOrUpdateDomain(t *testing.T) {
|
|||||||
t.Fatalf("expected to create an account for a user %s", userId)
|
t.Fatalf("expected to create an account for a user %s", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.Domain != domain {
|
if account != nil && account.Domain != domain {
|
||||||
t.Errorf("setting account domain failed, expected %s, got %s", domain, account.Domain)
|
t.Errorf("setting account domain failed, expected %s, got %s", domain, account.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,7 +857,7 @@ func TestAccountManager_SetOrUpdateDomain(t *testing.T) {
|
|||||||
t.Fatalf("expected to get an account for a user %s", userId)
|
t.Fatalf("expected to get an account for a user %s", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.Domain != domain {
|
if account != nil && account.Domain != domain {
|
||||||
t.Errorf("updating domain. expected %s got %s", domain, account.Domain)
|
t.Errorf("updating domain. expected %s got %s", domain, account.Domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -835,13 +877,12 @@ func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if account == nil {
|
if account == nil {
|
||||||
t.Fatalf("expected to create an account for a user %s", userId)
|
t.Fatalf("expected to create an account for a user %s", userId)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accountId := account.Id
|
_, err = manager.GetAccountByUserOrAccountID("", account.Id, "")
|
||||||
|
|
||||||
_, err = manager.GetAccountByUserOrAccountID("", accountId, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", accountId)
|
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", account.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = manager.GetAccountByUserOrAccountID("", "", "")
|
_, err = manager.GetAccountByUserOrAccountID("", "", "")
|
||||||
@@ -1124,7 +1165,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
|||||||
updMsg := manager.peersUpdateManager.CreateChannel(peer1.ID)
|
updMsg := manager.peersUpdateManager.CreateChannel(peer1.ID)
|
||||||
defer manager.peersUpdateManager.CloseChannel(peer1.ID)
|
defer manager.peersUpdateManager.CloseChannel(peer1.ID)
|
||||||
|
|
||||||
group := Group{
|
group := group.Group{
|
||||||
ID: "group-id",
|
ID: "group-id",
|
||||||
Name: "GroupA",
|
Name: "GroupA",
|
||||||
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
|
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
|
||||||
@@ -1417,7 +1458,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
Peers: map[string]*nbpeer.Peer{
|
Peers: map[string]*nbpeer.Peer{
|
||||||
"peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
"peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
||||||
},
|
},
|
||||||
Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
Groups: map[string]*group.Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
||||||
Routes: map[string]*route.Route{
|
Routes: map[string]*route.Route{
|
||||||
"route-1": {
|
"route-1": {
|
||||||
ID: "route-1",
|
ID: "route-1",
|
||||||
@@ -1518,7 +1559,7 @@ func TestAccount_Copy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Groups: map[string]*Group{
|
Groups: map[string]*group.Group{
|
||||||
"group1": {
|
"group1": {
|
||||||
ID: "group1",
|
ID: "group1",
|
||||||
Peers: []string{"peer1"},
|
Peers: []string{"peer1"},
|
||||||
@@ -2112,8 +2153,8 @@ func TestAccount_SetJWTGroups(t *testing.T) {
|
|||||||
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
|
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
|
||||||
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
|
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
|
||||||
},
|
},
|
||||||
Groups: map[string]*Group{
|
Groups: map[string]*group.Group{
|
||||||
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{}},
|
"group1": {ID: "group1", Name: "group1", Issued: group.GroupIssuedAPI, Peers: []string{}},
|
||||||
},
|
},
|
||||||
Settings: &Settings{GroupsPropagationEnabled: true},
|
Settings: &Settings{GroupsPropagationEnabled: true},
|
||||||
Users: map[string]*User{
|
Users: map[string]*User{
|
||||||
@@ -2160,10 +2201,10 @@ func TestAccount_UserGroupsAddToPeers(t *testing.T) {
|
|||||||
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
|
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
|
||||||
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
|
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
|
||||||
},
|
},
|
||||||
Groups: map[string]*Group{
|
Groups: map[string]*group.Group{
|
||||||
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{}},
|
"group1": {ID: "group1", Name: "group1", Issued: group.GroupIssuedAPI, Peers: []string{}},
|
||||||
"group2": {ID: "group2", Name: "group2", Issued: GroupIssuedAPI, Peers: []string{}},
|
"group2": {ID: "group2", Name: "group2", Issued: group.GroupIssuedAPI, Peers: []string{}},
|
||||||
"group3": {ID: "group3", Name: "group3", Issued: GroupIssuedAPI, Peers: []string{}},
|
"group3": {ID: "group3", Name: "group3", Issued: group.GroupIssuedAPI, Peers: []string{}},
|
||||||
},
|
},
|
||||||
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
|
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
|
||||||
}
|
}
|
||||||
@@ -2196,10 +2237,10 @@ func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
|
|||||||
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
|
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
|
||||||
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
|
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
|
||||||
},
|
},
|
||||||
Groups: map[string]*Group{
|
Groups: map[string]*group.Group{
|
||||||
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3"}},
|
"group1": {ID: "group1", Name: "group1", Issued: group.GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3"}},
|
||||||
"group2": {ID: "group2", Name: "group2", Issued: GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3", "peer4", "peer5"}},
|
"group2": {ID: "group2", Name: "group2", Issued: group.GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3", "peer4", "peer5"}},
|
||||||
"group3": {ID: "group3", Name: "group3", Issued: GroupIssuedAPI, Peers: []string{"peer4", "peer5"}},
|
"group3": {ID: "group3", Name: "group3", Issued: group.GroupIssuedAPI, Peers: []string{"peer4", "peer5"}},
|
||||||
},
|
},
|
||||||
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
|
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
|
||||||
}
|
}
|
||||||
@@ -2223,7 +2264,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false)
|
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStore(t *testing.T) (Store, error) {
|
func createStore(t *testing.T) (Store, error) {
|
||||||
|
|||||||
@@ -11,133 +11,134 @@ type Code struct {
|
|||||||
Code string
|
Code string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Existing consts must not be changed, as this will break the compatibility with the existing data
|
||||||
const (
|
const (
|
||||||
// PeerAddedByUser indicates that a user added a new peer to the system
|
// PeerAddedByUser indicates that a user added a new peer to the system
|
||||||
PeerAddedByUser Activity = iota
|
PeerAddedByUser Activity = 0
|
||||||
// PeerAddedWithSetupKey indicates that a new peer joined the system using a setup key
|
// PeerAddedWithSetupKey indicates that a new peer joined the system using a setup key
|
||||||
PeerAddedWithSetupKey
|
PeerAddedWithSetupKey Activity = 1
|
||||||
// UserJoined indicates that a new user joined the account
|
// UserJoined indicates that a new user joined the account
|
||||||
UserJoined
|
UserJoined Activity = 2
|
||||||
// UserInvited indicates that a new user was invited to join the account
|
// UserInvited indicates that a new user was invited to join the account
|
||||||
UserInvited
|
UserInvited Activity = 3
|
||||||
// AccountCreated indicates that a new account has been created
|
// AccountCreated indicates that a new account has been created
|
||||||
AccountCreated
|
AccountCreated Activity = 4
|
||||||
// PeerRemovedByUser indicates that a user removed a peer from the system
|
// PeerRemovedByUser indicates that a user removed a peer from the system
|
||||||
PeerRemovedByUser
|
PeerRemovedByUser Activity = 5
|
||||||
// RuleAdded indicates that a user added a new rule
|
// RuleAdded indicates that a user added a new rule
|
||||||
RuleAdded
|
RuleAdded Activity = 6
|
||||||
// RuleUpdated indicates that a user updated a rule
|
// RuleUpdated indicates that a user updated a rule
|
||||||
RuleUpdated
|
RuleUpdated Activity = 7
|
||||||
// RuleRemoved indicates that a user removed a rule
|
// RuleRemoved indicates that a user removed a rule
|
||||||
RuleRemoved
|
RuleRemoved Activity = 8
|
||||||
// PolicyAdded indicates that a user added a new policy
|
// PolicyAdded indicates that a user added a new policy
|
||||||
PolicyAdded
|
PolicyAdded Activity = 9
|
||||||
// PolicyUpdated indicates that a user updated a policy
|
// PolicyUpdated indicates that a user updated a policy
|
||||||
PolicyUpdated
|
PolicyUpdated Activity = 10
|
||||||
// PolicyRemoved indicates that a user removed a policy
|
// PolicyRemoved indicates that a user removed a policy
|
||||||
PolicyRemoved
|
PolicyRemoved Activity = 11
|
||||||
// SetupKeyCreated indicates that a user created a new setup key
|
// SetupKeyCreated indicates that a user created a new setup key
|
||||||
SetupKeyCreated
|
SetupKeyCreated Activity = 12
|
||||||
// SetupKeyUpdated indicates that a user updated a setup key
|
// SetupKeyUpdated indicates that a user updated a setup key
|
||||||
SetupKeyUpdated
|
SetupKeyUpdated Activity = 13
|
||||||
// SetupKeyRevoked indicates that a user revoked a setup key
|
// SetupKeyRevoked indicates that a user revoked a setup key
|
||||||
SetupKeyRevoked
|
SetupKeyRevoked Activity = 14
|
||||||
// SetupKeyOverused indicates that setup key usage exhausted
|
// SetupKeyOverused indicates that setup key usage exhausted
|
||||||
SetupKeyOverused
|
SetupKeyOverused Activity = 15
|
||||||
// GroupCreated indicates that a user created a group
|
// GroupCreated indicates that a user created a group
|
||||||
GroupCreated
|
GroupCreated Activity = 16
|
||||||
// GroupUpdated indicates that a user updated a group
|
// GroupUpdated indicates that a user updated a group
|
||||||
GroupUpdated
|
GroupUpdated Activity = 17
|
||||||
// GroupAddedToPeer indicates that a user added group to a peer
|
// GroupAddedToPeer indicates that a user added group to a peer
|
||||||
GroupAddedToPeer
|
GroupAddedToPeer Activity = 18
|
||||||
// GroupRemovedFromPeer indicates that a user removed peer group
|
// GroupRemovedFromPeer indicates that a user removed peer group
|
||||||
GroupRemovedFromPeer
|
GroupRemovedFromPeer Activity = 19
|
||||||
// GroupAddedToUser indicates that a user added group to a user
|
// GroupAddedToUser indicates that a user added group to a user
|
||||||
GroupAddedToUser
|
GroupAddedToUser Activity = 20
|
||||||
// GroupRemovedFromUser indicates that a user removed a group from a user
|
// GroupRemovedFromUser indicates that a user removed a group from a user
|
||||||
GroupRemovedFromUser
|
GroupRemovedFromUser Activity = 21
|
||||||
// UserRoleUpdated indicates that a user changed the role of a user
|
// UserRoleUpdated indicates that a user changed the role of a user
|
||||||
UserRoleUpdated
|
UserRoleUpdated Activity = 22
|
||||||
// GroupAddedToSetupKey indicates that a user added group to a setup key
|
// GroupAddedToSetupKey indicates that a user added group to a setup key
|
||||||
GroupAddedToSetupKey
|
GroupAddedToSetupKey Activity = 23
|
||||||
// GroupRemovedFromSetupKey indicates that a user removed a group from a setup key
|
// GroupRemovedFromSetupKey indicates that a user removed a group from a setup key
|
||||||
GroupRemovedFromSetupKey
|
GroupRemovedFromSetupKey Activity = 24
|
||||||
// GroupAddedToDisabledManagementGroups indicates that a user added a group to the DNS setting Disabled management groups
|
// GroupAddedToDisabledManagementGroups indicates that a user added a group to the DNS setting Disabled management groups
|
||||||
GroupAddedToDisabledManagementGroups
|
GroupAddedToDisabledManagementGroups Activity = 25
|
||||||
// GroupRemovedFromDisabledManagementGroups indicates that a user removed a group from the DNS setting Disabled management groups
|
// GroupRemovedFromDisabledManagementGroups indicates that a user removed a group from the DNS setting Disabled management groups
|
||||||
GroupRemovedFromDisabledManagementGroups
|
GroupRemovedFromDisabledManagementGroups Activity = 26
|
||||||
// RouteCreated indicates that a user created a route
|
// RouteCreated indicates that a user created a route
|
||||||
RouteCreated
|
RouteCreated Activity = 27
|
||||||
// RouteRemoved indicates that a user deleted a route
|
// RouteRemoved indicates that a user deleted a route
|
||||||
RouteRemoved
|
RouteRemoved Activity = 28
|
||||||
// RouteUpdated indicates that a user updated a route
|
// RouteUpdated indicates that a user updated a route
|
||||||
RouteUpdated
|
RouteUpdated Activity = 29
|
||||||
// PeerSSHEnabled indicates that a user enabled SSH server on a peer
|
// PeerSSHEnabled indicates that a user enabled SSH server on a peer
|
||||||
PeerSSHEnabled
|
PeerSSHEnabled Activity = 30
|
||||||
// PeerSSHDisabled indicates that a user disabled SSH server on a peer
|
// PeerSSHDisabled indicates that a user disabled SSH server on a peer
|
||||||
PeerSSHDisabled
|
PeerSSHDisabled Activity = 31
|
||||||
// PeerRenamed indicates that a user renamed a peer
|
// PeerRenamed indicates that a user renamed a peer
|
||||||
PeerRenamed
|
PeerRenamed Activity = 32
|
||||||
// PeerLoginExpirationEnabled indicates that a user enabled login expiration of a peer
|
// PeerLoginExpirationEnabled indicates that a user enabled login expiration of a peer
|
||||||
PeerLoginExpirationEnabled
|
PeerLoginExpirationEnabled Activity = 33
|
||||||
// PeerLoginExpirationDisabled indicates that a user disabled login expiration of a peer
|
// PeerLoginExpirationDisabled indicates that a user disabled login expiration of a peer
|
||||||
PeerLoginExpirationDisabled
|
PeerLoginExpirationDisabled Activity = 34
|
||||||
// NameserverGroupCreated indicates that a user created a nameservers group
|
// NameserverGroupCreated indicates that a user created a nameservers group
|
||||||
NameserverGroupCreated
|
NameserverGroupCreated Activity = 35
|
||||||
// NameserverGroupDeleted indicates that a user deleted a nameservers group
|
// NameserverGroupDeleted indicates that a user deleted a nameservers group
|
||||||
NameserverGroupDeleted
|
NameserverGroupDeleted Activity = 36
|
||||||
// NameserverGroupUpdated indicates that a user updated a nameservers group
|
// NameserverGroupUpdated indicates that a user updated a nameservers group
|
||||||
NameserverGroupUpdated
|
NameserverGroupUpdated Activity = 37
|
||||||
// AccountPeerLoginExpirationEnabled indicates that a user enabled peer login expiration for the account
|
// AccountPeerLoginExpirationEnabled indicates that a user enabled peer login expiration for the account
|
||||||
AccountPeerLoginExpirationEnabled
|
AccountPeerLoginExpirationEnabled Activity = 38
|
||||||
// AccountPeerLoginExpirationDisabled indicates that a user disabled peer login expiration for the account
|
// AccountPeerLoginExpirationDisabled indicates that a user disabled peer login expiration for the account
|
||||||
AccountPeerLoginExpirationDisabled
|
AccountPeerLoginExpirationDisabled Activity = 39
|
||||||
// AccountPeerLoginExpirationDurationUpdated indicates that a user updated peer login expiration duration for the account
|
// AccountPeerLoginExpirationDurationUpdated indicates that a user updated peer login expiration duration for the account
|
||||||
AccountPeerLoginExpirationDurationUpdated
|
AccountPeerLoginExpirationDurationUpdated Activity = 40
|
||||||
// PersonalAccessTokenCreated indicates that a user created a personal access token
|
// PersonalAccessTokenCreated indicates that a user created a personal access token
|
||||||
PersonalAccessTokenCreated
|
PersonalAccessTokenCreated Activity = 41
|
||||||
// PersonalAccessTokenDeleted indicates that a user deleted a personal access token
|
// PersonalAccessTokenDeleted indicates that a user deleted a personal access token
|
||||||
PersonalAccessTokenDeleted
|
PersonalAccessTokenDeleted Activity = 42
|
||||||
// ServiceUserCreated indicates that a user created a service user
|
// ServiceUserCreated indicates that a user created a service user
|
||||||
ServiceUserCreated
|
ServiceUserCreated Activity = 43
|
||||||
// ServiceUserDeleted indicates that a user deleted a service user
|
// ServiceUserDeleted indicates that a user deleted a service user
|
||||||
ServiceUserDeleted
|
ServiceUserDeleted Activity = 44
|
||||||
// UserBlocked indicates that a user blocked another user
|
// UserBlocked indicates that a user blocked another user
|
||||||
UserBlocked
|
UserBlocked Activity = 45
|
||||||
// UserUnblocked indicates that a user unblocked another user
|
// UserUnblocked indicates that a user unblocked another user
|
||||||
UserUnblocked
|
UserUnblocked Activity = 46
|
||||||
// UserDeleted indicates that a user deleted another user
|
// UserDeleted indicates that a user deleted another user
|
||||||
UserDeleted
|
UserDeleted Activity = 47
|
||||||
// GroupDeleted indicates that a user deleted group
|
// GroupDeleted indicates that a user deleted group
|
||||||
GroupDeleted
|
GroupDeleted Activity = 48
|
||||||
// UserLoggedInPeer indicates that user logged in their peer with an interactive SSO login
|
// UserLoggedInPeer indicates that user logged in their peer with an interactive SSO login
|
||||||
UserLoggedInPeer
|
UserLoggedInPeer Activity = 49
|
||||||
// PeerLoginExpired indicates that the user peer login has been expired and peer disconnected
|
// PeerLoginExpired indicates that the user peer login has been expired and peer disconnected
|
||||||
PeerLoginExpired
|
PeerLoginExpired Activity = 50
|
||||||
// DashboardLogin indicates that the user logged in to the dashboard
|
// DashboardLogin indicates that the user logged in to the dashboard
|
||||||
DashboardLogin
|
DashboardLogin Activity = 51
|
||||||
// IntegrationCreated indicates that the user created an integration
|
// IntegrationCreated indicates that the user created an integration
|
||||||
IntegrationCreated
|
IntegrationCreated Activity = 52
|
||||||
// IntegrationUpdated indicates that the user updated an integration
|
// IntegrationUpdated indicates that the user updated an integration
|
||||||
IntegrationUpdated
|
IntegrationUpdated Activity = 53
|
||||||
// IntegrationDeleted indicates that the user deleted an integration
|
// IntegrationDeleted indicates that the user deleted an integration
|
||||||
IntegrationDeleted
|
IntegrationDeleted Activity = 54
|
||||||
// AccountPeerApprovalEnabled indicates that the user enabled peer approval for the account
|
// AccountPeerApprovalEnabled indicates that the user enabled peer approval for the account
|
||||||
AccountPeerApprovalEnabled
|
AccountPeerApprovalEnabled Activity = 55
|
||||||
// AccountPeerApprovalDisabled indicates that the user disabled peer approval for the account
|
// AccountPeerApprovalDisabled indicates that the user disabled peer approval for the account
|
||||||
AccountPeerApprovalDisabled
|
AccountPeerApprovalDisabled Activity = 56
|
||||||
// PeerApproved indicates that the peer has been approved
|
// PeerApproved indicates that the peer has been approved
|
||||||
PeerApproved
|
PeerApproved Activity = 57
|
||||||
// PeerApprovalRevoked indicates that the peer approval has been revoked
|
// PeerApprovalRevoked indicates that the peer approval has been revoked
|
||||||
PeerApprovalRevoked
|
PeerApprovalRevoked Activity = 58
|
||||||
// TransferredOwnerRole indicates that the user transferred the owner role of the account
|
// TransferredOwnerRole indicates that the user transferred the owner role of the account
|
||||||
TransferredOwnerRole
|
TransferredOwnerRole Activity = 59
|
||||||
// PostureCheckCreated indicates that the user created a posture check
|
// PostureCheckCreated indicates that the user created a posture check
|
||||||
PostureCheckCreated
|
PostureCheckCreated Activity = 60
|
||||||
// PostureCheckUpdated indicates that the user updated a posture check
|
// PostureCheckUpdated indicates that the user updated a posture check
|
||||||
PostureCheckUpdated
|
PostureCheckUpdated Activity = 61
|
||||||
// PostureCheckDeleted indicates that the user deleted a posture check
|
// PostureCheckDeleted indicates that the user deleted a posture check
|
||||||
PostureCheckDeleted
|
PostureCheckDeleted Activity = 62
|
||||||
)
|
)
|
||||||
|
|
||||||
var activityMap = map[Activity]Code{
|
var activityMap = map[Activity]Code{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/dns"
|
"github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/group"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
@@ -193,7 +194,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false)
|
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDNSStore(t *testing.T) (Store, error) {
|
func createDNSStore(t *testing.T) (Store, error) {
|
||||||
@@ -278,13 +279,13 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newGroup1 := &Group{
|
newGroup1 := &group.Group{
|
||||||
ID: dnsGroup1ID,
|
ID: dnsGroup1ID,
|
||||||
Peers: []string{peer1.ID},
|
Peers: []string{peer1.ID},
|
||||||
Name: dnsGroup1ID,
|
Name: dnsGroup1ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
newGroup2 := &Group{
|
newGroup2 := &group.Group{
|
||||||
ID: dnsGroup2ID,
|
ID: dnsGroup2ID,
|
||||||
Name: dnsGroup2ID,
|
Name: dnsGroup2ID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func (e *EphemeralManager) cleanup() {
|
|||||||
log.Debugf("delete ephemeral peer: %s", id)
|
log.Debugf("delete ephemeral peer: %s", id)
|
||||||
err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("failed to delete ephemeral peer: %s", err)
|
log.Errorf("failed to delete ephemeral peer: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
@@ -170,7 +171,7 @@ func restore(file string) (*FileStore, error) {
|
|||||||
// Set API as issuer for groups which has not this field
|
// Set API as issuer for groups which has not this field
|
||||||
for _, group := range account.Groups {
|
for _, group := range account.Groups {
|
||||||
if group.Issued == "" {
|
if group.Issued == "" {
|
||||||
group.Issued = GroupIssuedAPI
|
group.Issued = nbgroup.GroupIssuedAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/group"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
@@ -188,7 +189,7 @@ func TestStore(t *testing.T) {
|
|||||||
Name: "peer name",
|
Name: "peer name",
|
||||||
Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
|
Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
|
||||||
}
|
}
|
||||||
account.Groups["all"] = &Group{
|
account.Groups["all"] = &group.Group{
|
||||||
ID: "all",
|
ID: "all",
|
||||||
Name: "all",
|
Name: "all",
|
||||||
Peers: []string{"testpeer"},
|
Peers: []string{"testpeer"},
|
||||||
@@ -320,7 +321,7 @@ func TestRestoreGroups_Migration(t *testing.T) {
|
|||||||
|
|
||||||
// create default group
|
// create default group
|
||||||
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||||
account.Groups = map[string]*Group{
|
account.Groups = map[string]*group.Group{
|
||||||
"cfefqs706sqkneg59g3g": {
|
"cfefqs706sqkneg59g3g": {
|
||||||
ID: "cfefqs706sqkneg59g3g",
|
ID: "cfefqs706sqkneg59g3g",
|
||||||
Name: "All",
|
Name: "All",
|
||||||
@@ -336,7 +337,7 @@ func TestRestoreGroups_Migration(t *testing.T) {
|
|||||||
account = store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
account = store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||||
|
|
||||||
require.Contains(t, account.Groups, "cfefqs706sqkneg59g3g", "failed to restore a FileStore file - missing Account Groups")
|
require.Contains(t, account.Groups, "cfefqs706sqkneg59g3g", "failed to restore a FileStore file - missing Account Groups")
|
||||||
require.Equal(t, GroupIssuedAPI, account.Groups["cfefqs706sqkneg59g3g"].Issued, "default group should has API issued mark")
|
require.Equal(t, group.GroupIssuedAPI, account.Groups["cfefqs706sqkneg59g3g"].Issued, "default group should has API issued mark")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAccountByPrivateDomain(t *testing.T) {
|
func TestGetAccountByPrivateDomain(t *testing.T) {
|
||||||
@@ -384,6 +385,7 @@ func TestFileStore_GetAccount(t *testing.T) {
|
|||||||
expected := accounts.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
expected := accounts.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||||
if expected == nil {
|
if expected == nil {
|
||||||
t.Fatalf("expected account doesn't exist")
|
t.Fatalf("expected account doesn't exist")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := store.GetAccount(expected.Id)
|
account, err := store.GetAccount(expected.Id)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,51 +20,8 @@ func (e *GroupLinkError) Error() string {
|
|||||||
return fmt.Sprintf("group has been linked to %s: %s", e.Resource, e.Name)
|
return fmt.Sprintf("group has been linked to %s: %s", e.Resource, e.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
GroupIssuedAPI = "api"
|
|
||||||
GroupIssuedJWT = "jwt"
|
|
||||||
GroupIssuedIntegration = "integration"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Group of the peers for ACL
|
|
||||||
type Group struct {
|
|
||||||
// ID of the group
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// AccountID is a reference to Account that this object belongs
|
|
||||||
AccountID string `json:"-" gorm:"index"`
|
|
||||||
|
|
||||||
// Name visible in the UI
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Issued defines how this group was created (enum of "api", "integration" or "jwt")
|
|
||||||
Issued string
|
|
||||||
|
|
||||||
// Peers list of the group
|
|
||||||
Peers []string `gorm:"serializer:json"`
|
|
||||||
|
|
||||||
IntegrationReference IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventMeta returns activity event meta related to the group
|
|
||||||
func (g *Group) EventMeta() map[string]any {
|
|
||||||
return map[string]any{"name": g.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) Copy() *Group {
|
|
||||||
group := &Group{
|
|
||||||
ID: g.ID,
|
|
||||||
Name: g.Name,
|
|
||||||
Issued: g.Issued,
|
|
||||||
Peers: make([]string, len(g.Peers)),
|
|
||||||
IntegrationReference: g.IntegrationReference,
|
|
||||||
}
|
|
||||||
copy(group.Peers, g.Peers)
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroup object of the peers
|
// GetGroup object of the peers
|
||||||
func (am *DefaultAccountManager) GetGroup(accountID, groupID, userID string) (*Group, error) {
|
func (am *DefaultAccountManager) GetGroup(accountID, groupID, userID string) (*nbgroup.Group, error) {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -90,7 +48,7 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID, userID string) (*G
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAllGroups returns all groups in an account
|
// GetAllGroups returns all groups in an account
|
||||||
func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) ([]*Group, error) {
|
func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) ([]*nbgroup.Group, error) {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -108,7 +66,7 @@ func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) (
|
|||||||
return nil, status.Errorf(status.PermissionDenied, "groups are blocked for users")
|
return nil, status.Errorf(status.PermissionDenied, "groups are blocked for users")
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := make([]*Group, 0, len(account.Groups))
|
groups := make([]*nbgroup.Group, 0, len(account.Groups))
|
||||||
for _, item := range account.Groups {
|
for _, item := range account.Groups {
|
||||||
groups = append(groups, item)
|
groups = append(groups, item)
|
||||||
}
|
}
|
||||||
@@ -117,7 +75,7 @@ func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupByName filters all groups in an account by name and returns the one with the most peers
|
// GetGroupByName filters all groups in an account by name and returns the one with the most peers
|
||||||
func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*Group, error) {
|
func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*nbgroup.Group, error) {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -126,7 +84,7 @@ func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*G
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingGroups := make([]*Group, 0)
|
matchingGroups := make([]*nbgroup.Group, 0)
|
||||||
for _, group := range account.Groups {
|
for _, group := range account.Groups {
|
||||||
if group.Name == groupName {
|
if group.Name == groupName {
|
||||||
matchingGroups = append(matchingGroups, group)
|
matchingGroups = append(matchingGroups, group)
|
||||||
@@ -138,7 +96,7 @@ func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*G
|
|||||||
}
|
}
|
||||||
|
|
||||||
maxPeers := -1
|
maxPeers := -1
|
||||||
var groupWithMostPeers *Group
|
var groupWithMostPeers *nbgroup.Group
|
||||||
for i, group := range matchingGroups {
|
for i, group := range matchingGroups {
|
||||||
if len(group.Peers) > maxPeers {
|
if len(group.Peers) > maxPeers {
|
||||||
maxPeers = len(group.Peers)
|
maxPeers = len(group.Peers)
|
||||||
@@ -150,7 +108,7 @@ func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*G
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveGroup object of the peers
|
// SaveGroup object of the peers
|
||||||
func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *Group) error {
|
func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *nbgroup.Group) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -159,11 +117,11 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if newGroup.ID == "" && newGroup.Issued != GroupIssuedAPI {
|
if newGroup.ID == "" && newGroup.Issued != nbgroup.GroupIssuedAPI {
|
||||||
return status.Errorf(status.InvalidArgument, "%s group without ID set", newGroup.Issued)
|
return status.Errorf(status.InvalidArgument, "%s group without ID set", newGroup.Issued)
|
||||||
}
|
}
|
||||||
|
|
||||||
if newGroup.ID == "" && newGroup.Issued == GroupIssuedAPI {
|
if newGroup.ID == "" && newGroup.Issued == nbgroup.GroupIssuedAPI {
|
||||||
|
|
||||||
existingGroup, err := account.FindGroupByName(newGroup.Name)
|
existingGroup, err := account.FindGroupByName(newGroup.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -270,7 +228,7 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// disable a deleting integration group if the initiator is not an admin service user
|
// disable a deleting integration group if the initiator is not an admin service user
|
||||||
if g.Issued == GroupIssuedIntegration {
|
if g.Issued == nbgroup.GroupIssuedIntegration {
|
||||||
executingUser := account.Users[userId]
|
executingUser := account.Users[userId]
|
||||||
if executingUser == nil {
|
if executingUser == nil {
|
||||||
return status.Errorf(status.NotFound, "user not found")
|
return status.Errorf(status.NotFound, "user not found")
|
||||||
@@ -340,6 +298,15 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check integrated peer validator groups
|
||||||
|
if account.Settings.Extra != nil {
|
||||||
|
for _, integratedPeerValidatorGroups := range account.Settings.Extra.IntegratedValidatorGroups {
|
||||||
|
if groupID == integratedPeerValidatorGroups {
|
||||||
|
return &GroupLinkError{"integrated validator", g.Name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete(account.Groups, groupID)
|
delete(account.Groups, groupID)
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
@@ -355,7 +322,7 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListGroups objects of the peers
|
// ListGroups objects of the peers
|
||||||
func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error) {
|
func (am *DefaultAccountManager) ListGroups(accountID string) ([]*nbgroup.Group, error) {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -364,7 +331,7 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := make([]*Group, 0, len(account.Groups))
|
groups := make([]*nbgroup.Group, 0, len(account.Groups))
|
||||||
for _, item := range account.Groups {
|
for _, item := range account.Groups {
|
||||||
groups = append(groups, item)
|
groups = append(groups, item)
|
||||||
}
|
}
|
||||||
|
|||||||
46
management/server/group/group.go
Normal file
46
management/server/group/group.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/management/server/integration_reference"
|
||||||
|
|
||||||
|
const (
|
||||||
|
GroupIssuedAPI = "api"
|
||||||
|
GroupIssuedJWT = "jwt"
|
||||||
|
GroupIssuedIntegration = "integration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Group of the peers for ACL
|
||||||
|
type Group struct {
|
||||||
|
// ID of the group
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `json:"-" gorm:"index"`
|
||||||
|
|
||||||
|
// Name visible in the UI
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Issued defines how this group was created (enum of "api", "integration" or "jwt")
|
||||||
|
Issued string
|
||||||
|
|
||||||
|
// Peers list of the group
|
||||||
|
Peers []string `gorm:"serializer:json"`
|
||||||
|
|
||||||
|
IntegrationReference integration_reference.IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventMeta returns activity event meta related to the group
|
||||||
|
func (g *Group) EventMeta() map[string]any {
|
||||||
|
return map[string]any{"name": g.Name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Copy() *Group {
|
||||||
|
group := &Group{
|
||||||
|
ID: g.ID,
|
||||||
|
Name: g.Name,
|
||||||
|
Issued: g.Issued,
|
||||||
|
Peers: make([]string, len(g.Peers)),
|
||||||
|
IntegrationReference: g.IntegrationReference,
|
||||||
|
}
|
||||||
|
copy(group.Peers, g.Peers)
|
||||||
|
return group
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
@@ -24,22 +25,22 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
|||||||
t.Error("failed to init testing account")
|
t.Error("failed to init testing account")
|
||||||
}
|
}
|
||||||
for _, group := range account.Groups {
|
for _, group := range account.Groups {
|
||||||
group.Issued = GroupIssuedIntegration
|
group.Issued = nbgroup.GroupIssuedIntegration
|
||||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("should allow to create %s groups", GroupIssuedIntegration)
|
t.Errorf("should allow to create %s groups", nbgroup.GroupIssuedIntegration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, group := range account.Groups {
|
for _, group := range account.Groups {
|
||||||
group.Issued = GroupIssuedJWT
|
group.Issued = nbgroup.GroupIssuedJWT
|
||||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("should allow to create %s groups", GroupIssuedJWT)
|
t.Errorf("should allow to create %s groups", nbgroup.GroupIssuedJWT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, group := range account.Groups {
|
for _, group := range account.Groups {
|
||||||
group.Issued = GroupIssuedAPI
|
group.Issued = nbgroup.GroupIssuedAPI
|
||||||
group.ID = ""
|
group.ID = ""
|
||||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -129,51 +130,51 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
accountID := "testingAcc"
|
accountID := "testingAcc"
|
||||||
domain := "example.com"
|
domain := "example.com"
|
||||||
|
|
||||||
groupForRoute := &Group{
|
groupForRoute := &nbgroup.Group{
|
||||||
ID: "grp-for-route",
|
ID: "grp-for-route",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
Name: "Group for route",
|
Name: "Group for route",
|
||||||
Issued: GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
groupForNameServerGroups := &Group{
|
groupForNameServerGroups := &nbgroup.Group{
|
||||||
ID: "grp-for-name-server-grp",
|
ID: "grp-for-name-server-grp",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
Name: "Group for name server groups",
|
Name: "Group for name server groups",
|
||||||
Issued: GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
groupForPolicies := &Group{
|
groupForPolicies := &nbgroup.Group{
|
||||||
ID: "grp-for-policies",
|
ID: "grp-for-policies",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
Name: "Group for policies",
|
Name: "Group for policies",
|
||||||
Issued: GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
groupForSetupKeys := &Group{
|
groupForSetupKeys := &nbgroup.Group{
|
||||||
ID: "grp-for-keys",
|
ID: "grp-for-keys",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
Name: "Group for setup keys",
|
Name: "Group for setup keys",
|
||||||
Issued: GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
groupForUsers := &Group{
|
groupForUsers := &nbgroup.Group{
|
||||||
ID: "grp-for-users",
|
ID: "grp-for-users",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
Name: "Group for users",
|
Name: "Group for users",
|
||||||
Issued: GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
groupForIntegration := &Group{
|
groupForIntegration := &nbgroup.Group{
|
||||||
ID: "grp-for-integration",
|
ID: "grp-for-integration",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
Name: "Group for users integration",
|
Name: "Group for users integration",
|
||||||
Issued: GroupIssuedIntegration,
|
Issued: nbgroup.GroupIssuedIntegration,
|
||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -343,10 +343,18 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
|||||||
userID := ""
|
userID := ""
|
||||||
// JWT token is not always provided, it is fine for userID to be empty cuz it might be that peer is already registered,
|
// JWT token is not always provided, it is fine for userID to be empty cuz it might be that peer is already registered,
|
||||||
// or it uses a setup key to register.
|
// or it uses a setup key to register.
|
||||||
|
|
||||||
if loginReq.GetJwtToken() != "" {
|
if loginReq.GetJwtToken() != "" {
|
||||||
userID, err = s.validateToken(loginReq.GetJwtToken())
|
for i := 0; i < 3; i++ {
|
||||||
|
userID, err = s.validateToken(loginReq.GetJwtToken())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Warnf("failed validating JWT token sent from peer %s with error %v. "+
|
||||||
|
"Trying again as it may be due to the IdP cache issue", peerKey, err)
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed validating JWT token sent from peer %s", peerKey)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,6 +369,7 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
|||||||
Meta: extractPeerMeta(loginReq),
|
Meta: extractPeerMeta(loginReq),
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
SetupKey: loginReq.GetSetupKey(),
|
SetupKey: loginReq.GetSetupKey(),
|
||||||
|
ConnectionIP: realIP,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func initAccountsTestData(account *server.Account, admin *server.User) *Accounts
|
|||||||
|
|
||||||
func TestAccounts_AccountsHandler(t *testing.T) {
|
func TestAccounts_AccountsHandler(t *testing.T) {
|
||||||
accountID := "test_account"
|
accountID := "test_account"
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user", "account_id")
|
||||||
|
|
||||||
sr := func(v string) *string { return &v }
|
sr := func(v string) *string { return &v }
|
||||||
br := func(v bool) *bool { return &v }
|
br := func(v bool) *bool { return &v }
|
||||||
|
|||||||
@@ -355,6 +355,7 @@ components:
|
|||||||
- user_id
|
- user_id
|
||||||
- version
|
- version
|
||||||
- ui_version
|
- ui_version
|
||||||
|
- approval_required
|
||||||
AccessiblePeer:
|
AccessiblePeer:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/PeerMinimum'
|
- $ref: '#/components/schemas/PeerMinimum'
|
||||||
|
|||||||
@@ -470,7 +470,7 @@ type Peer struct {
|
|||||||
AccessiblePeers []AccessiblePeer `json:"accessible_peers"`
|
AccessiblePeers []AccessiblePeer `json:"accessible_peers"`
|
||||||
|
|
||||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired bool `json:"approval_required"`
|
||||||
|
|
||||||
// CityName Commonly used English name of the city
|
// CityName Commonly used English name of the city
|
||||||
CityName CityName `json:"city_name"`
|
CityName CityName `json:"city_name"`
|
||||||
@@ -539,7 +539,7 @@ type Peer struct {
|
|||||||
// PeerBase defines model for PeerBase.
|
// PeerBase defines model for PeerBase.
|
||||||
type PeerBase struct {
|
type PeerBase struct {
|
||||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired bool `json:"approval_required"`
|
||||||
|
|
||||||
// CityName Commonly used English name of the city
|
// CityName Commonly used English name of the city
|
||||||
CityName CityName `json:"city_name"`
|
CityName CityName `json:"city_name"`
|
||||||
@@ -611,7 +611,7 @@ type PeerBatch struct {
|
|||||||
AccessiblePeersCount int `json:"accessible_peers_count"`
|
AccessiblePeersCount int `json:"accessible_peers_count"`
|
||||||
|
|
||||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired bool `json:"approval_required"`
|
||||||
|
|
||||||
// CityName Commonly used English name of the city
|
// CityName Commonly used English name of the city
|
||||||
CityName CityName `json:"city_name"`
|
CityName CityName `json:"city_name"`
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ var testingDNSSettingsAccount = &server.Account{
|
|||||||
Id: testDNSSettingsAccountID,
|
Id: testDNSSettingsAccountID,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
testDNSSettingsUserID: server.NewAdminUser("test_user"),
|
testDNSSettingsUserID: server.NewAdminUser("test_user", "account_id"),
|
||||||
},
|
},
|
||||||
DNSSettings: baseExistingDNSSettings,
|
DNSSettings: baseExistingDNSSettings,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ func TestEvents_GetEvents(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
accountID := "test_account"
|
accountID := "test_account"
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user", "account_id")
|
||||||
events := generateEvents(accountID, adminUser.Id)
|
events := generateEvents(accountID, adminUser.Id)
|
||||||
handler := initEventsTestData(accountID, adminUser, events...)
|
handler := initEventsTestData(accountID, adminUser, events...)
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func initGeolocationTestData(t *testing.T) *GeolocationsHandler {
|
|||||||
return &GeolocationsHandler{
|
return &GeolocationsHandler{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
user := server.NewAdminUser("test_user")
|
user := server.NewAdminUser("test_user", "account_id")
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/util"
|
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GroupsHandler is a handler that returns groups of the account
|
// GroupsHandler is a handler that returns groups of the account
|
||||||
@@ -110,7 +110,7 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
peers = *req.Peers
|
peers = *req.Peers
|
||||||
}
|
}
|
||||||
group := server.Group{
|
group := nbgroup.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
@@ -154,10 +154,10 @@ func (h *GroupsHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
peers = *req.Peers
|
peers = *req.Peers
|
||||||
}
|
}
|
||||||
group := server.Group{
|
group := nbgroup.Group{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
Issued: server.GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
|
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
|
||||||
@@ -240,7 +240,7 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
func toGroupResponse(account *server.Account, group *nbgroup.Group) *api.Group {
|
||||||
cache := make(map[string]api.PeerMinimum)
|
cache := make(map[string]api.PeerMinimum)
|
||||||
gr := api.Group{
|
gr := api.Group{
|
||||||
Id: group.ID,
|
Id: group.ID,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/magiconair/properties/assert"
|
"github.com/magiconair/properties/assert"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
"github.com/netbirdio/netbird/management/server/http/util"
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
@@ -28,30 +29,30 @@ var TestPeers = map[string]*nbpeer.Peer{
|
|||||||
"B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")},
|
"B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")},
|
||||||
}
|
}
|
||||||
|
|
||||||
func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandler {
|
func initGroupTestData(user *server.User, _ ...*nbgroup.Group) *GroupsHandler {
|
||||||
return &GroupsHandler{
|
return &GroupsHandler{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
SaveGroupFunc: func(accountID, userID string, group *server.Group) error {
|
SaveGroupFunc: func(accountID, userID string, group *nbgroup.Group) error {
|
||||||
if !strings.HasPrefix(group.ID, "id-") {
|
if !strings.HasPrefix(group.ID, "id-") {
|
||||||
group.ID = "id-was-set"
|
group.ID = "id-was-set"
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetGroupFunc: func(_, groupID, _ string) (*server.Group, error) {
|
GetGroupFunc: func(_, groupID, _ string) (*nbgroup.Group, error) {
|
||||||
if groupID != "idofthegroup" {
|
if groupID != "idofthegroup" {
|
||||||
return nil, status.Errorf(status.NotFound, "not found")
|
return nil, status.Errorf(status.NotFound, "not found")
|
||||||
}
|
}
|
||||||
if groupID == "id-jwt-group" {
|
if groupID == "id-jwt-group" {
|
||||||
return &server.Group{
|
return &nbgroup.Group{
|
||||||
ID: "id-jwt-group",
|
ID: "id-jwt-group",
|
||||||
Name: "Default Group",
|
Name: "Default Group",
|
||||||
Issued: server.GroupIssuedJWT,
|
Issued: nbgroup.GroupIssuedJWT,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &server.Group{
|
return &nbgroup.Group{
|
||||||
ID: "idofthegroup",
|
ID: "idofthegroup",
|
||||||
Name: "Group",
|
Name: "Group",
|
||||||
Issued: server.GroupIssuedAPI,
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
@@ -62,10 +63,10 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
|
|||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
user.Id: user,
|
user.Id: user,
|
||||||
},
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*nbgroup.Group{
|
||||||
"id-jwt-group": {ID: "id-jwt-group", Name: "From JWT", Issued: server.GroupIssuedJWT},
|
"id-jwt-group": {ID: "id-jwt-group", Name: "From JWT", Issued: nbgroup.GroupIssuedJWT},
|
||||||
"id-existed": {ID: "id-existed", Peers: []string{"A", "B"}, Issued: server.GroupIssuedAPI},
|
"id-existed": {ID: "id-existed", Peers: []string{"A", "B"}, Issued: nbgroup.GroupIssuedAPI},
|
||||||
"id-all": {ID: "id-all", Name: "All", Issued: server.GroupIssuedAPI},
|
"id-all": {ID: "id-all", Name: "All", Issued: nbgroup.GroupIssuedAPI},
|
||||||
},
|
},
|
||||||
}, user, nil
|
}, user, nil
|
||||||
},
|
},
|
||||||
@@ -118,12 +119,12 @@ func TestGetGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
group := &server.Group{
|
group := &nbgroup.Group{
|
||||||
ID: "idofthegroup",
|
ID: "idofthegroup",
|
||||||
Name: "Group",
|
Name: "Group",
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user", "account_id")
|
||||||
p := initGroupTestData(adminUser, group)
|
p := initGroupTestData(adminUser, group)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
@@ -153,7 +154,7 @@ func TestGetGroup(t *testing.T) {
|
|||||||
t.Fatalf("I don't know what I expected; %v", err)
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got := &server.Group{}
|
got := &nbgroup.Group{}
|
||||||
if err = json.Unmarshal(content, &got); err != nil {
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
}
|
}
|
||||||
@@ -245,7 +246,7 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user", "account_id")
|
||||||
p := initGroupTestData(adminUser)
|
p := initGroupTestData(adminUser)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
@@ -323,7 +324,7 @@ func TestDeleteGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user", "account_id")
|
||||||
p := initGroupTestData(adminUser)
|
p := initGroupTestData(adminUser)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
s "github.com/netbirdio/netbird/management/server"
|
s "github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
|
"github.com/netbirdio/netbird/management/server/integrated_validator"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
@@ -39,7 +39,7 @@ type emptyObject struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// APIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
// APIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
||||||
func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationManager *geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg) (http.Handler, error) {
|
func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationManager *geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg, integratedValidator integrated_validator.IntegratedValidator) (http.Handler, error) {
|
||||||
claimsExtractor := jwtclaims.NewClaimsExtractor(
|
claimsExtractor := jwtclaims.NewClaimsExtractor(
|
||||||
jwtclaims.WithAudience(authCfg.Audience),
|
jwtclaims.WithAudience(authCfg.Audience),
|
||||||
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||||
@@ -76,7 +76,7 @@ func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationMa
|
|||||||
AuthCfg: authCfg,
|
AuthCfg: authCfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := integrations.RegisterHandlers(ctx, prefix, api.Router, accountManager, claimsExtractor); err != nil {
|
if _, err := integrations.RegisterHandlers(ctx, prefix, api.Router, accountManager, claimsExtractor, integratedValidator); err != nil {
|
||||||
return nil, fmt.Errorf("register integrations endpoints: %w", err)
|
return nil, fmt.Errorf("register integrations endpoints: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ var testingNSAccount = &server.Account{
|
|||||||
Id: testNSGroupAccountID,
|
Id: testNSGroupAccountID,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
"test_user": server.NewAdminUser("test_user"),
|
"test_user": server.NewAdminUser("test_user", "account_id"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
"github.com/netbirdio/netbird/management/server/http/util"
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
@@ -61,10 +63,18 @@ func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w
|
|||||||
|
|
||||||
groupsInfo := toGroupsInfo(account.Groups, peer.ID)
|
groupsInfo := toGroupsInfo(account.Groups, peer.ID)
|
||||||
|
|
||||||
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
validPeers, err := h.accountManager.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to list appreoved peers: %v", err)
|
||||||
|
util.WriteError(fmt.Errorf("internal error"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain(), validPeers)
|
||||||
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
||||||
|
|
||||||
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers))
|
_, valid := validPeers[peer.ID]
|
||||||
|
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers, valid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
|
func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -75,11 +85,18 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name,
|
update := &nbpeer.Peer{
|
||||||
LoginExpirationEnabled: req.LoginExpirationEnabled}
|
ID: peerID,
|
||||||
|
SSHEnabled: req.SshEnabled,
|
||||||
|
Name: req.Name,
|
||||||
|
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
||||||
|
}
|
||||||
|
|
||||||
if req.ApprovalRequired != nil {
|
if req.ApprovalRequired != nil {
|
||||||
update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired}
|
// todo: looks like that we reset all status property, is it right?
|
||||||
|
update.Status = &nbpeer.PeerStatus{
|
||||||
|
RequiresApproval: *req.ApprovalRequired,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
|
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
|
||||||
@@ -91,15 +108,24 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
|||||||
|
|
||||||
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
|
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
|
||||||
|
|
||||||
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
validPeers, err := h.accountManager.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to list appreoved peers: %v", err)
|
||||||
|
util.WriteError(fmt.Errorf("internal error"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain(), validPeers)
|
||||||
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
||||||
|
|
||||||
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers))
|
_, valid := validPeers[peer.ID]
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers, valid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
||||||
err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("failed to delete peer: %v", err)
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -138,46 +164,68 @@ func (h *PeersHandler) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// GetAllPeers returns a list of all peers associated with a provided account
|
// GetAllPeers returns a list of all peers associated with a provided account
|
||||||
func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
if r.Method != http.MethodGet {
|
||||||
case http.MethodGet:
|
|
||||||
claims := h.claimsExtractor.FromRequestContext(r)
|
|
||||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
|
||||||
if err != nil {
|
|
||||||
util.WriteError(err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
peers, err := h.accountManager.GetPeers(account.Id, user.Id)
|
|
||||||
if err != nil {
|
|
||||||
util.WriteError(err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsDomain := h.accountManager.GetDNSDomain()
|
|
||||||
|
|
||||||
respBody := make([]*api.PeerBatch, 0, len(peers))
|
|
||||||
for _, peer := range peers {
|
|
||||||
peerToReturn, err := h.checkPeerStatus(peer)
|
|
||||||
if err != nil {
|
|
||||||
util.WriteError(err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
|
|
||||||
|
|
||||||
accessiblePeerNumbers := h.accessiblePeersNumber(account, peer.ID)
|
|
||||||
|
|
||||||
respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
|
|
||||||
}
|
|
||||||
util.WriteJSONObject(w, respBody)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peers, err := h.accountManager.GetPeers(account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
|
||||||
|
respBody := make([]*api.PeerBatch, 0, len(peers))
|
||||||
|
for _, peer := range peers {
|
||||||
|
peerToReturn, err := h.checkPeerStatus(peer)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
|
||||||
|
|
||||||
|
accessiblePeerNumbers, _ := h.accessiblePeersNumber(account, peer.ID)
|
||||||
|
|
||||||
|
respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
|
||||||
|
}
|
||||||
|
|
||||||
|
validPeersMap, err := h.accountManager.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to list appreoved peers: %v", err)
|
||||||
|
util.WriteError(fmt.Errorf("internal error"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.setApprovalRequiredFlag(respBody, validPeersMap)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, respBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) accessiblePeersNumber(account *server.Account, peerID string) int {
|
func (h *PeersHandler) accessiblePeersNumber(account *server.Account, peerID string) (int, error) {
|
||||||
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
validatedPeersMap, err := h.accountManager.GetValidatedPeers(account)
|
||||||
return len(netMap.Peers) + len(netMap.OfflinePeers)
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain(), validatedPeersMap)
|
||||||
|
return len(netMap.Peers) + len(netMap.OfflinePeers), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PeersHandler) setApprovalRequiredFlag(respBody []*api.PeerBatch, approvedPeersMap map[string]struct{}) {
|
||||||
|
for _, peer := range respBody {
|
||||||
|
_, ok := approvedPeersMap[peer.Id]
|
||||||
|
if !ok {
|
||||||
|
peer.ApprovalRequired = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.AccessiblePeer {
|
func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.AccessiblePeer {
|
||||||
@@ -206,7 +254,7 @@ func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.Access
|
|||||||
return accessiblePeers
|
return accessiblePeers
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMinimum {
|
func toGroupsInfo(groups map[string]*nbgroup.Group, peerID string) []api.GroupMinimum {
|
||||||
var groupsInfo []api.GroupMinimum
|
var groupsInfo []api.GroupMinimum
|
||||||
groupsChecked := make(map[string]struct{})
|
groupsChecked := make(map[string]struct{})
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
@@ -230,7 +278,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin
|
|||||||
return groupsInfo
|
return groupsInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
|
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer, approved bool) *api.Peer {
|
||||||
osVersion := peer.Meta.OSVersion
|
osVersion := peer.Meta.OSVersion
|
||||||
if osVersion == "" {
|
if osVersion == "" {
|
||||||
osVersion = peer.Meta.Core
|
osVersion = peer.Meta.Core
|
||||||
@@ -257,7 +305,7 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
|||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
LoginExpired: peer.Status.LoginExpired,
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
AccessiblePeers: accessiblePeer,
|
AccessiblePeers: accessiblePeer,
|
||||||
ApprovalRequired: &peer.Status.RequiresApproval,
|
ApprovalRequired: !approved,
|
||||||
CountryCode: peer.Location.CountryCode,
|
CountryCode: peer.Location.CountryCode,
|
||||||
CityName: peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
}
|
}
|
||||||
@@ -290,7 +338,6 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
|||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
LoginExpired: peer.Status.LoginExpired,
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
AccessiblePeersCount: accessiblePeersCount,
|
AccessiblePeersCount: accessiblePeersCount,
|
||||||
ApprovalRequired: &peer.Status.RequiresApproval,
|
|
||||||
CountryCode: peer.Location.CountryCode,
|
CountryCode: peer.Location.CountryCode,
|
||||||
CityName: peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler {
|
|||||||
return "netbird.selfhosted"
|
return "netbird.selfhosted"
|
||||||
},
|
},
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
user := server.NewAdminUser("test_user")
|
user := server.NewAdminUser("test_user", "account_id")
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
|
||||||
@@ -44,14 +45,14 @@ func initPoliciesTestData(policies ...*server.Policy) *Policies {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
user := server.NewAdminUser("test_user")
|
user := server.NewAdminUser("test_user", "account_id")
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Policies: []*server.Policy{
|
Policies: []*server.Policy{
|
||||||
{ID: "id-existed"},
|
{ID: "id-existed"},
|
||||||
},
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*nbgroup.Group{
|
||||||
"F": {ID: "F"},
|
"F": {ID: "F"},
|
||||||
"G": {ID: "G"},
|
"G": {ID: "G"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func initPostureChecksTestData(postureChecks ...*posture.Checks) *PostureChecksH
|
|||||||
return accountPostureChecks, nil
|
return accountPostureChecks, nil
|
||||||
},
|
},
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
user := server.NewAdminUser("test_user")
|
user := server.NewAdminUser("test_user", "account_id")
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ var testingAccount = &server.Account{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
"test_user": server.NewAdminUser("test_user"),
|
"test_user": server.NewAdminUser("test_user", "account_id"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -44,7 +43,7 @@ func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.Setup
|
|||||||
SetupKeys: map[string]*server.SetupKey{
|
SetupKeys: map[string]*server.SetupKey{
|
||||||
defaultKey.Key: defaultKey,
|
defaultKey.Key: defaultKey,
|
||||||
},
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*nbgroup.Group{
|
||||||
"group-1": {ID: "group-1", Peers: []string{"A", "B"}},
|
"group-1": {ID: "group-1", Peers: []string{"A", "B"}},
|
||||||
"id-all": {ID: "id-all", Name: "All"},
|
"id-all": {ID: "id-all", Name: "All"},
|
||||||
},
|
},
|
||||||
@@ -98,7 +97,7 @@ func TestSetupKeysHandlers(t *testing.T) {
|
|||||||
defaultSetupKey := server.GenerateDefaultSetupKey()
|
defaultSetupKey := server.GenerateDefaultSetupKey()
|
||||||
defaultSetupKey.Id = existingSetupKeyID
|
defaultSetupKey.Id = existingSetupKeyID
|
||||||
|
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user", "account_id")
|
||||||
|
|
||||||
newSetupKey := server.GenerateSetupKey(newSetupKeyName, server.SetupKeyReusable, 0, []string{"group-1"},
|
newSetupKey := server.GenerateSetupKey(newSetupKeyName, server.SetupKeyReusable, 0, []string{"group-1"},
|
||||||
server.SetupKeyUnlimitedUsage, true)
|
server.SetupKeyUnlimitedUsage, true)
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ func WriteError(err error, w http.ResponseWriter) {
|
|||||||
httpStatus = http.StatusUnprocessableEntity
|
httpStatus = http.StatusUnprocessableEntity
|
||||||
case status.Unauthorized:
|
case status.Unauthorized:
|
||||||
httpStatus = http.StatusUnauthorized
|
httpStatus = http.StatusUnauthorized
|
||||||
|
case status.BadRequest:
|
||||||
|
httpStatus = http.StatusBadRequest
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
msg = strings.ToLower(err.Error())
|
msg = strings.ToLower(err.Error())
|
||||||
|
|||||||
@@ -115,7 +115,15 @@ func (ac *AzureCredentials) requestJWTToken() (*http.Response, error) {
|
|||||||
data.Set("client_id", ac.clientConfig.ClientID)
|
data.Set("client_id", ac.clientConfig.ClientID)
|
||||||
data.Set("client_secret", ac.clientConfig.ClientSecret)
|
data.Set("client_secret", ac.clientConfig.ClientSecret)
|
||||||
data.Set("grant_type", ac.clientConfig.GrantType)
|
data.Set("grant_type", ac.clientConfig.GrantType)
|
||||||
data.Set("scope", "https://graph.microsoft.com/.default")
|
parsedURL, err := url.Parse(ac.clientConfig.GraphAPIEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get base url and add "/.default" as scope
|
||||||
|
baseURL := parsedURL.Scheme + "://" + parsedURL.Host
|
||||||
|
scopeURL := baseURL + "/.default"
|
||||||
|
data.Set("scope", scopeURL)
|
||||||
|
|
||||||
payload := strings.NewReader(data.Encode())
|
payload := strings.NewReader(data.Encode())
|
||||||
req, err := http.NewRequest(http.MethodPost, ac.clientConfig.TokenEndpoint, payload)
|
req, err := http.NewRequest(http.MethodPost, ac.clientConfig.TokenEndpoint, payload)
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ func (om *OktaManager) DeleteUser(userID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseOktaUserToUserData parse okta user to UserData.
|
// parseOktaUser parse okta user to UserData.
|
||||||
func parseOktaUser(user *okta.User) (*UserData, error) {
|
func parseOktaUser(user *okta.User) (*UserData, error) {
|
||||||
var oktaUser struct {
|
var oktaUser struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|||||||
80
management/server/integrated_validator.go
Normal file
80
management/server/integrated_validator.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/google/martian/v3/log"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateIntegratedValidatorGroups updates the integrated validator groups for a specified account.
|
||||||
|
// It retrieves the account associated with the provided userID, then updates the integrated validator groups
|
||||||
|
// with the provided list of group ids. The updated account is then saved.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - accountID: The ID of the account for which integrated validator groups are to be updated.
|
||||||
|
// - userID: The ID of the user whose account is being updated.
|
||||||
|
// - groups: A slice of strings representing the ids of integrated validator groups to be updated.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - error: An error if any occurred during the process, otherwise returns nil
|
||||||
|
func (am *DefaultAccountManager) UpdateIntegratedValidatorGroups(accountID string, userID string, groups []string) error {
|
||||||
|
ok, err := am.GroupValidation(accountID, groups)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("error validating groups: %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
log.Debugf("invalid groups")
|
||||||
|
return errors.New("invalid groups")
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
a, err := am.Store.GetAccountByUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var extra *account.ExtraSettings
|
||||||
|
|
||||||
|
if a.Settings.Extra != nil {
|
||||||
|
extra = a.Settings.Extra
|
||||||
|
} else {
|
||||||
|
extra = &account.ExtraSettings{}
|
||||||
|
a.Settings.Extra = extra
|
||||||
|
}
|
||||||
|
extra.IntegratedValidatorGroups = groups
|
||||||
|
return am.Store.SaveAccount(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) GroupValidation(accountId string, groups []string) (bool, error) {
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
accountsGroups, err := am.ListGroups(accountId)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
var found bool
|
||||||
|
for _, accountGroup := range accountsGroups {
|
||||||
|
if accountGroup.ID == group {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) GetValidatedPeers(account *Account) (map[string]struct{}, error) {
|
||||||
|
return am.integratedPeerValidator.GetValidatedPeers(account.Id, account.Groups, account.Peers, account.Settings.Extra)
|
||||||
|
}
|
||||||
19
management/server/integrated_validator/interface.go
Normal file
19
management/server/integrated_validator/interface.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package integrated_validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntegratedValidator interface exists to avoid the circle dependencies
|
||||||
|
type IntegratedValidator interface {
|
||||||
|
ValidateExtraSettings(newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error
|
||||||
|
ValidatePeer(update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error)
|
||||||
|
PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer
|
||||||
|
IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool)
|
||||||
|
GetValidatedPeers(accountID string, groups map[string]*nbgroup.Group, peers map[string]*nbpeer.Peer, extraSettings *account.ExtraSettings) (map[string]struct{}, error)
|
||||||
|
PeerDeleted(accountID, peerID string) error
|
||||||
|
SetPeerInvalidationListener(fn func(accountID string))
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package integration_reference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntegrationReference holds the reference to a particular integration
|
||||||
|
type IntegrationReference struct {
|
||||||
|
ID int
|
||||||
|
IntegrationType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ir IntegrationReference) String() string {
|
||||||
|
return fmt.Sprintf("%s:%d", ir.IntegrationType, ir.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ir IntegrationReference) CacheKey(path ...string) string {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return ir.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%s", ir.String(), strings.Join(path, ":"))
|
||||||
|
}
|
||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -19,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -413,7 +412,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error)
|
|||||||
peersUpdateManager := NewPeersUpdateManager(nil)
|
peersUpdateManager := NewPeersUpdateManager(nil)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted",
|
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted",
|
||||||
eventStore, nil, false)
|
eventStore, nil, false, MocIntegratedValidator{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,22 @@ import (
|
|||||||
sync2 "sync"
|
sync2 "sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
|
|
||||||
pb "github.com/golang/protobuf/proto" //nolint
|
pb "github.com/golang/protobuf/proto" //nolint
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/encryption"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/group"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -448,6 +446,43 @@ var _ = Describe("Management service", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type MocIntegratedValidator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MocIntegratedValidator) ValidateExtraSettings(newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MocIntegratedValidator) ValidatePeer(update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error) {
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MocIntegratedValidator) GetValidatedPeers(accountID string, groups map[string]*group.Group, peers map[string]*nbpeer.Peer, extraSettings *account.ExtraSettings) (map[string]struct{}, error) {
|
||||||
|
validatedPeers := make(map[string]struct{})
|
||||||
|
for p := range peers {
|
||||||
|
validatedPeers[p] = struct{}{}
|
||||||
|
}
|
||||||
|
return validatedPeers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer {
|
||||||
|
return peer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool) {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) PeerDeleted(_, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) SetPeerInvalidationListener(func(accountID string)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MocIntegratedValidator) Stop() {}
|
||||||
|
|
||||||
func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse {
|
func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse {
|
||||||
defer GinkgoRecover()
|
defer GinkgoRecover()
|
||||||
|
|
||||||
@@ -504,7 +539,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
|||||||
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted",
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted",
|
||||||
eventStore, nil, false)
|
eventStore, nil, false, MocIntegratedValidator{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating a manager: %v", err)
|
log.Fatalf("failed creating a manager: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/group"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/posture"
|
"github.com/netbirdio/netbird/management/server/posture"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
@@ -32,7 +33,7 @@ func (mockDatasource) GetAllAccounts() []*server.Account {
|
|||||||
UsedTimes: 1,
|
UsedTimes: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*group.Group{
|
||||||
"1": {},
|
"1": {},
|
||||||
"2": {},
|
"2": {},
|
||||||
},
|
},
|
||||||
@@ -117,7 +118,7 @@ func (mockDatasource) GetAllAccounts() []*server.Account {
|
|||||||
UsedTimes: 1,
|
UsedTimes: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*group.Group{
|
||||||
"1": {},
|
"1": {},
|
||||||
"2": {},
|
"2": {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
@@ -31,12 +32,12 @@ type MockAccountManager struct {
|
|||||||
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
||||||
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
||||||
AddPeerFunc func(setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *server.NetworkMap, error)
|
AddPeerFunc func(setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *server.NetworkMap, error)
|
||||||
GetGroupFunc func(accountID, groupID, userID string) (*server.Group, error)
|
GetGroupFunc func(accountID, groupID, userID string) (*group.Group, error)
|
||||||
GetAllGroupsFunc func(accountID, userID string) ([]*server.Group, error)
|
GetAllGroupsFunc func(accountID, userID string) ([]*group.Group, error)
|
||||||
GetGroupByNameFunc func(accountID, groupName string) (*server.Group, error)
|
GetGroupByNameFunc func(accountID, groupName string) (*group.Group, error)
|
||||||
SaveGroupFunc func(accountID, userID string, group *server.Group) error
|
SaveGroupFunc func(accountID, userID string, group *group.Group) error
|
||||||
DeleteGroupFunc func(accountID, userId, groupID string) error
|
DeleteGroupFunc func(accountID, userId, groupID string) error
|
||||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
ListGroupsFunc func(accountID string) ([]*group.Group, error)
|
||||||
GroupAddPeerFunc func(accountID, groupID, peerID string) error
|
GroupAddPeerFunc func(accountID, groupID, peerID string) error
|
||||||
GroupDeletePeerFunc func(accountID, groupID, peerID string) error
|
GroupDeletePeerFunc func(accountID, groupID, peerID string) error
|
||||||
DeleteRuleFunc func(accountID, ruleID, userID string) error
|
DeleteRuleFunc func(accountID, ruleID, userID string) error
|
||||||
@@ -91,10 +92,20 @@ type MockAccountManager struct {
|
|||||||
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
|
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
|
||||||
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
||||||
GetIdpManagerFunc func() idp.Manager
|
GetIdpManagerFunc func() idp.Manager
|
||||||
|
UpdateIntegratedValidatorGroupsFunc func(accountID string, userID string, groups []string) error
|
||||||
|
GroupValidationFunc func(accountId string, groups []string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *MockAccountManager) GetValidatedPeers(account *server.Account) (map[string]struct{}, error) {
|
||||||
|
approvedPeers := make(map[string]struct{})
|
||||||
|
for id := range account.Peers {
|
||||||
|
approvedPeers[id] = struct{}{}
|
||||||
|
}
|
||||||
|
return approvedPeers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroup mock implementation of GetGroup from server.AccountManager interface
|
// GetGroup mock implementation of GetGroup from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetGroup(accountId, groupID, userID string) (*server.Group, error) {
|
func (am *MockAccountManager) GetGroup(accountId, groupID, userID string) (*group.Group, error) {
|
||||||
if am.GetGroupFunc != nil {
|
if am.GetGroupFunc != nil {
|
||||||
return am.GetGroupFunc(accountId, groupID, userID)
|
return am.GetGroupFunc(accountId, groupID, userID)
|
||||||
}
|
}
|
||||||
@@ -102,7 +113,7 @@ func (am *MockAccountManager) GetGroup(accountId, groupID, userID string) (*serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAllGroups mock implementation of GetAllGroups from server.AccountManager interface
|
// GetAllGroups mock implementation of GetAllGroups from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetAllGroups(accountID, userID string) ([]*server.Group, error) {
|
func (am *MockAccountManager) GetAllGroups(accountID, userID string) ([]*group.Group, error) {
|
||||||
if am.GetAllGroupsFunc != nil {
|
if am.GetAllGroupsFunc != nil {
|
||||||
return am.GetAllGroupsFunc(accountID, userID)
|
return am.GetAllGroupsFunc(accountID, userID)
|
||||||
}
|
}
|
||||||
@@ -261,7 +272,7 @@ func (am *MockAccountManager) AddPeer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupByName mock implementation of GetGroupByName from server.AccountManager interface
|
// GetGroupByName mock implementation of GetGroupByName from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetGroupByName(accountID, groupName string) (*server.Group, error) {
|
func (am *MockAccountManager) GetGroupByName(accountID, groupName string) (*group.Group, error) {
|
||||||
if am.GetGroupFunc != nil {
|
if am.GetGroupFunc != nil {
|
||||||
return am.GetGroupByNameFunc(accountID, groupName)
|
return am.GetGroupByNameFunc(accountID, groupName)
|
||||||
}
|
}
|
||||||
@@ -269,7 +280,7 @@ func (am *MockAccountManager) GetGroupByName(accountID, groupName string) (*serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
||||||
func (am *MockAccountManager) SaveGroup(accountID, userID string, group *server.Group) error {
|
func (am *MockAccountManager) SaveGroup(accountID, userID string, group *group.Group) error {
|
||||||
if am.SaveGroupFunc != nil {
|
if am.SaveGroupFunc != nil {
|
||||||
return am.SaveGroupFunc(accountID, userID, group)
|
return am.SaveGroupFunc(accountID, userID, group)
|
||||||
}
|
}
|
||||||
@@ -285,7 +296,7 @@ func (am *MockAccountManager) DeleteGroup(accountId, userId, groupID string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListGroups mock implementation of ListGroups from server.AccountManager interface
|
// ListGroups mock implementation of ListGroups from server.AccountManager interface
|
||||||
func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, error) {
|
func (am *MockAccountManager) ListGroups(accountID string) ([]*group.Group, error) {
|
||||||
if am.ListGroupsFunc != nil {
|
if am.ListGroupsFunc != nil {
|
||||||
return am.ListGroupsFunc(accountID)
|
return am.ListGroupsFunc(accountID)
|
||||||
}
|
}
|
||||||
@@ -694,3 +705,19 @@ func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateIntegratedValidatorGroups mocks UpdateIntegratedApprovalGroups of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) UpdateIntegratedValidatorGroups(accountID string, userID string, groups []string) error {
|
||||||
|
if am.UpdateIntegratedValidatorGroupsFunc != nil {
|
||||||
|
return am.UpdateIntegratedValidatorGroupsFunc(accountID, userID, groups)
|
||||||
|
}
|
||||||
|
return status.Errorf(codes.Unimplemented, "method UpdateIntegratedValidatorGroups is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupValidation mocks GroupValidation of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GroupValidation(accountId string, groups []string) (bool, error) {
|
||||||
|
if am.GroupValidationFunc != nil {
|
||||||
|
return am.GroupValidationFunc(accountId, groups)
|
||||||
|
}
|
||||||
|
return false, status.Errorf(codes.Unimplemented, "method GroupValidation is not implemented")
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -261,7 +262,7 @@ func validateNSList(list []nbdns.NameServer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateGroups(list []string, groups map[string]*Group) error {
|
func validateGroups(list []string, groups map[string]*nbgroup.Group) error {
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
return status.Errorf(status.InvalidArgument, "the list of group IDs should not be empty")
|
return status.Errorf(status.InvalidArgument, "the list of group IDs should not be empty")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -759,7 +760,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false)
|
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNSStore(t *testing.T) (Store, error) {
|
func createNSStore(t *testing.T) (Store, error) {
|
||||||
@@ -831,12 +832,12 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
|
|||||||
|
|
||||||
account.NameServerGroups[existingNSGroup.ID] = &existingNSGroup
|
account.NameServerGroups[existingNSGroup.ID] = &existingNSGroup
|
||||||
|
|
||||||
newGroup1 := &Group{
|
newGroup1 := &nbgroup.Group{
|
||||||
ID: group1ID,
|
ID: group1ID,
|
||||||
Name: group1ID,
|
Name: group1ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
newGroup2 := &Group{
|
newGroup2 := &nbgroup.Group{
|
||||||
ID: group2ID,
|
ID: group2ID,
|
||||||
Name: group2ID,
|
Name: group2ID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/additions"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
|
// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
|
||||||
@@ -37,6 +33,8 @@ type PeerLogin struct {
|
|||||||
UserID string
|
UserID string
|
||||||
// SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
|
// SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
|
||||||
SetupKey string
|
SetupKey string
|
||||||
|
// ConnectionIP is the real IP of the peer
|
||||||
|
ConnectionIP net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
|
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
|
||||||
@@ -52,6 +50,10 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.P
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
approvedPeersMap, err := am.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
peers := make([]*nbpeer.Peer, 0)
|
peers := make([]*nbpeer.Peer, 0)
|
||||||
peersMap := make(map[string]*nbpeer.Peer)
|
peersMap := make(map[string]*nbpeer.Peer)
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.P
|
|||||||
|
|
||||||
// fetch all the peers that have access to the user's peers
|
// fetch all the peers that have access to the user's peers
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
aclPeers, _ := account.getPeerConnectionResources(peer.ID)
|
aclPeers, _ := account.getPeerConnectionResources(peer.ID, approvedPeersMap)
|
||||||
for _, p := range aclPeers {
|
for _, p := range aclPeers {
|
||||||
peersMap[p.ID] = p
|
peersMap[p.ID] = p
|
||||||
}
|
}
|
||||||
@@ -167,7 +169,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb
|
|||||||
return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID)
|
return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
update, err = additions.ValidatePeersUpdateRequest(update, peer, userID, accountID, am.eventStore, am.GetDNSDomain())
|
update, err = am.integratedPeerValidator.ValidatePeer(update, peer, userID, accountID, am.GetDNSDomain(), account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -244,6 +246,12 @@ func (am *DefaultAccountManager) deletePeers(account *Account, peerIDs []string,
|
|||||||
|
|
||||||
// the 2nd loop performs the actual modification
|
// the 2nd loop performs the actual modification
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
|
|
||||||
|
err := am.integratedPeerValidator.PeerDeleted(account.Id, peer.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
account.DeletePeer(peer.ID)
|
account.DeletePeer(peer.ID)
|
||||||
am.peersUpdateManager.SendUpdate(peer.ID,
|
am.peersUpdateManager.SendUpdate(peer.ID,
|
||||||
&UpdateMessage{
|
&UpdateMessage{
|
||||||
@@ -304,7 +312,17 @@ func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, erro
|
|||||||
if peer == nil {
|
if peer == nil {
|
||||||
return nil, status.Errorf(status.NotFound, "peer with ID %s not found", peerID)
|
return nil, status.Errorf(status.NotFound, "peer with ID %s not found", peerID)
|
||||||
}
|
}
|
||||||
return account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
|
||||||
|
groups := make(map[string][]string)
|
||||||
|
for groupID, group := range account.Groups {
|
||||||
|
groups[groupID] = group.Peers
|
||||||
|
}
|
||||||
|
|
||||||
|
validatedPeers, err := am.integratedPeerValidator.GetValidatedPeers(account.Id, account.Groups, account.Peers, account.Settings.Extra)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return account.GetPeerNetworkMap(peer.ID, am.dnsDomain, validatedPeers), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerNetwork returns the Network for a given peer
|
// GetPeerNetwork returns the Network for a given peer
|
||||||
@@ -433,10 +451,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
|||||||
CreatedAt: registrationTime,
|
CreatedAt: registrationTime,
|
||||||
LoginExpirationEnabled: addedByUser,
|
LoginExpirationEnabled: addedByUser,
|
||||||
Ephemeral: ephemeral,
|
Ephemeral: ephemeral,
|
||||||
}
|
Location: peer.Location,
|
||||||
|
|
||||||
if account.Settings.Extra != nil {
|
|
||||||
newPeer = additions.PreparePeer(newPeer, account.Settings.Extra)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add peer to 'All' group
|
// add peer to 'All' group
|
||||||
@@ -467,6 +482,8 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newPeer = am.integratedPeerValidator.PreparePeer(account.Id, newPeer, account.GetPeerGroupsList(newPeer.ID), account.Settings.Extra)
|
||||||
|
|
||||||
if addedByUser {
|
if addedByUser {
|
||||||
user, err := account.FindUser(userID)
|
user, err := account.FindUser(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -492,7 +509,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
|||||||
|
|
||||||
am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain)
|
approvedPeersMap, err := am.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain, approvedPeersMap)
|
||||||
return newPeer, networkMap, nil
|
return newPeer, networkMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,23 +550,53 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network
|
|||||||
if peerLoginExpired(peer, account) {
|
if peerLoginExpired(peer, account) {
|
||||||
return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more")
|
return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more")
|
||||||
}
|
}
|
||||||
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
|
||||||
|
peerNotValid, isStatusChanged := am.integratedPeerValidator.IsNotValidPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||||
|
if peerNotValid {
|
||||||
|
emptyMap := &NetworkMap{
|
||||||
|
Network: account.Network.Copy(),
|
||||||
|
}
|
||||||
|
return peer, emptyMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isStatusChanged {
|
||||||
|
am.updateAccountPeers(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
validPeersMap, err := am.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain, validPeersMap), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginPeer logs in or registers a peer.
|
// 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.
|
// 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(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) {
|
func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) {
|
||||||
account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey)
|
account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
|
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.
|
// we couldn't find this peer by its public key which can mean that peer hasn't been registered yet.
|
||||||
// Try registering it.
|
// Try registering it.
|
||||||
return am.AddPeer(login.SetupKey, login.UserID, &nbpeer.Peer{
|
newPeer := &nbpeer.Peer{
|
||||||
Key: login.WireGuardPubKey,
|
Key: login.WireGuardPubKey,
|
||||||
Meta: login.Meta,
|
Meta: login.Meta,
|
||||||
SSHKey: login.SSHKey,
|
SSHKey: login.SSHKey,
|
||||||
})
|
}
|
||||||
|
if am.geo != nil && login.ConnectionIP != nil {
|
||||||
|
location, err := am.geo.Lookup(login.ConnectionIP)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to get location for new peer realip: [%s]: %v", login.ConnectionIP.String(), err)
|
||||||
|
} else {
|
||||||
|
newPeer.Location.ConnectionIP = login.ConnectionIP
|
||||||
|
newPeer.Location.CountryCode = location.Country.ISOCode
|
||||||
|
newPeer.Location.CityName = location.City.Names.En
|
||||||
|
newPeer.Location.GeoNameID = location.City.GeonameID
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return am.AddPeer(login.SetupKey, login.UserID, newPeer)
|
||||||
}
|
}
|
||||||
log.Errorf("failed while logging in peer %s: %v", login.WireGuardPubKey, err)
|
log.Errorf("failed while logging in peer %s: %v", login.WireGuardPubKey, err)
|
||||||
return nil, nil, status.Errorf(status.Internal, "failed while logging in peer")
|
return nil, nil, status.Errorf(status.Internal, "failed while logging in peer")
|
||||||
@@ -595,6 +646,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
|
|||||||
am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
|
am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRequiresApproval, isStatusChanged := am.integratedPeerValidator.IsNotValidPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||||
peer, updated := updatePeerMeta(peer, login.Meta, account)
|
peer, updated := updatePeerMeta(peer, login.Meta, account)
|
||||||
if updated {
|
if updated {
|
||||||
shouldStoreAccount = true
|
shouldStoreAccount = true
|
||||||
@@ -612,10 +664,23 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateRemotePeers {
|
if updateRemotePeers || isStatusChanged {
|
||||||
am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
}
|
}
|
||||||
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
|
||||||
|
if isRequiresApproval {
|
||||||
|
emptyMap := &NetworkMap{
|
||||||
|
Network: account.Network.Copy(),
|
||||||
|
}
|
||||||
|
return peer, emptyMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
approvedPeersMap, err := am.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain, approvedPeersMap), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error {
|
func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error {
|
||||||
@@ -764,8 +829,13 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*nbp
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
approvedPeersMap, err := am.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range userPeers {
|
for _, p := range userPeers {
|
||||||
aclPeers, _ := account.getPeerConnectionResources(p.ID)
|
aclPeers, _ := account.getPeerConnectionResources(p.ID, approvedPeersMap)
|
||||||
for _, aclPeer := range aclPeers {
|
for _, aclPeer := range aclPeers {
|
||||||
if aclPeer.ID == peerID {
|
if aclPeer.ID == peerID {
|
||||||
return peer, nil
|
return peer, nil
|
||||||
@@ -789,8 +859,13 @@ func updatePeerMeta(peer *nbpeer.Peer, meta nbpeer.PeerSystemMeta, account *Acco
|
|||||||
func (am *DefaultAccountManager) updateAccountPeers(account *Account) {
|
func (am *DefaultAccountManager) updateAccountPeers(account *Account) {
|
||||||
peers := account.GetPeers()
|
peers := account.GetPeers()
|
||||||
|
|
||||||
|
approvedPeersMap, err := am.GetValidatedPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed send out updates to peers, failed to validate peer: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
remotePeerNetworkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain)
|
remotePeerNetworkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain, approvedPeersMap)
|
||||||
update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain())
|
update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain())
|
||||||
am.peersUpdateManager.SendUpdate(peer.ID, &UpdateMessage{Update: update})
|
am.peersUpdateManager.SendUpdate(peer.ID, &UpdateMessage{Update: update})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,8 +200,8 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
group1 Group
|
group1 nbgroup.Group
|
||||||
group2 Group
|
group2 nbgroup.Group
|
||||||
policy Policy
|
policy Policy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user