mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-27 04:36:37 +00:00
Compare commits
67 Commits
v0.21.0
...
feature/ke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d68a4a7d21 | ||
|
|
ca1722ed10 | ||
|
|
649dbf2bed | ||
|
|
70076b98d2 | ||
|
|
551455f314 | ||
|
|
a6431e053b | ||
|
|
520c7b5d37 | ||
|
|
e376541745 | ||
|
|
774d8e955c | ||
|
|
c20f98c8b6 | ||
|
|
20ae540fb1 | ||
|
|
58cfa2bb17 | ||
|
|
06005cc10e | ||
|
|
1a3e377304 | ||
|
|
dd29f4c01e | ||
|
|
cb7ecd1cc4 | ||
|
|
a4350c19e7 | ||
|
|
09ca2d222a | ||
|
|
f1b38dbe80 | ||
|
|
042f124702 | ||
|
|
b5d8142705 | ||
|
|
f45eb1a1da | ||
|
|
2567006412 | ||
|
|
b92107efc8 | ||
|
|
ff267768f0 | ||
|
|
5d19811331 | ||
|
|
697d41c94e | ||
|
|
75d541f967 | ||
|
|
481465e1ae | ||
|
|
7dfbb71f7a | ||
|
|
a5d14c92ff | ||
|
|
ce091ab42b | ||
|
|
d2fad1cfd9 | ||
|
|
f8da516128 | ||
|
|
c331cef242 | ||
|
|
0b5594f145 | ||
|
|
9beaa91db9 | ||
|
|
c8b4c08139 | ||
|
|
dad5501a44 | ||
|
|
1ced2462c1 | ||
|
|
64adaeb276 | ||
|
|
6e26d03fb8 | ||
|
|
493ddb4fe3 | ||
|
|
75fac258e7 | ||
|
|
bc8ee8fc3c | ||
|
|
3724323f76 | ||
|
|
3ef33874b1 | ||
|
|
a0296f7839 | ||
|
|
1d9feab2d9 | ||
|
|
2c9583dfe1 | ||
|
|
ef59001459 | ||
|
|
93608ae163 | ||
|
|
7d1b6ea1fc | ||
|
|
803bbe0fff | ||
|
|
675abbddf6 | ||
|
|
eac492be9b | ||
|
|
a0e133bd92 | ||
|
|
9460c4a91e | ||
|
|
bbf536be85 | ||
|
|
933fe1964a | ||
|
|
8f51985fa5 | ||
|
|
05e642103c | ||
|
|
f2df8f31cb | ||
|
|
dd69c1cd31 | ||
|
|
7c6d29c9c5 | ||
|
|
b50503f8b7 | ||
|
|
11a3fef5bc |
@@ -46,6 +46,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI_NETBIRD_DOMAIN: localhost
|
CI_NETBIRD_DOMAIN: localhost
|
||||||
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
||||||
|
CI_NETBIRD_AUTH_CLIENT_SECRET: testing.client.secret
|
||||||
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
||||||
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
CI_NETBIRD_USE_AUTH0: true
|
CI_NETBIRD_USE_AUTH0: true
|
||||||
@@ -58,6 +59,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI_NETBIRD_DOMAIN: localhost
|
CI_NETBIRD_DOMAIN: localhost
|
||||||
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
||||||
|
CI_NETBIRD_AUTH_CLIENT_SECRET: testing.client.secret
|
||||||
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
||||||
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
CI_NETBIRD_USE_AUTH0: true
|
CI_NETBIRD_USE_AUTH0: true
|
||||||
@@ -77,6 +79,7 @@ jobs:
|
|||||||
|
|
||||||
run: |
|
run: |
|
||||||
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
|
grep AUTH_CLIENT_SECRET docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_SECRET
|
||||||
grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY
|
grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY
|
||||||
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
||||||
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
||||||
|
|||||||
22
.github/workflows/update-docs.yml
vendored
Normal file
22
.github/workflows/update-docs.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: update docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
paths:
|
||||||
|
- 'management/server/http/api/openapi.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger_docs_api_update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
steps:
|
||||||
|
- name: Trigger API pages generation
|
||||||
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
|
with:
|
||||||
|
workflow: generate api pages
|
||||||
|
repo: netbirdio/docs
|
||||||
|
ref: "refs/heads/main"
|
||||||
|
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||||
|
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -7,8 +7,15 @@ bin/
|
|||||||
conf.json
|
conf.json
|
||||||
http-cmds.sh
|
http-cmds.sh
|
||||||
infrastructure_files/management.json
|
infrastructure_files/management.json
|
||||||
|
infrastructure_files/management-*.json
|
||||||
infrastructure_files/docker-compose.yml
|
infrastructure_files/docker-compose.yml
|
||||||
|
infrastructure_files/openid-configuration.json
|
||||||
|
infrastructure_files/turnserver.conf
|
||||||
|
management/management
|
||||||
|
client/client
|
||||||
|
client/client.exe
|
||||||
*.syso
|
*.syso
|
||||||
client/.distfiles/
|
client/.distfiles/
|
||||||
infrastructure_files/setup.env
|
infrastructure_files/setup.env
|
||||||
|
infrastructure_files/setup-*.env
|
||||||
.vscode
|
.vscode
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ type Client struct {
|
|||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
func NewClient(cfgFile, deviceName string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, routeListener RouteListener) *Client {
|
func NewClient(cfgFile, deviceName string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, routeListener RouteListener) *Client {
|
||||||
lvl, _ := log.ParseLevel("trace")
|
|
||||||
log.SetLevel(lvl)
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
cfgFile: cfgFile,
|
cfgFile: cfgFile,
|
||||||
deviceName: deviceName,
|
deviceName: deviceName,
|
||||||
@@ -107,6 +104,11 @@ func (c *Client) Stop() {
|
|||||||
c.ctxCancel()
|
c.ctxCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTraceLogLevel configure the logger to trace level
|
||||||
|
func (c *Client) SetTraceLogLevel() {
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
}
|
||||||
|
|
||||||
// PeersList return with the list of the PeerInfos
|
// PeersList return with the list of the PeerInfos
|
||||||
func (c *Client) PeersList() *PeerInfoArray {
|
func (c *Client) PeersList() *PeerInfoArray {
|
||||||
|
|
||||||
@@ -118,11 +120,9 @@ func (c *Client) PeersList() *PeerInfoArray {
|
|||||||
p.IP,
|
p.IP,
|
||||||
p.FQDN,
|
p.FQDN,
|
||||||
p.ConnStatus.String(),
|
p.ConnStatus.String(),
|
||||||
p.Direct,
|
|
||||||
}
|
}
|
||||||
peerInfos[n] = pi
|
peerInfos[n] = pi
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PeerInfoArray{items: peerInfos}
|
return &PeerInfoArray{items: peerInfos}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
client/android/gomobile.go
Normal file
5
client/android/gomobile.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package android
|
||||||
|
|
||||||
|
import _ "golang.org/x/mobile/bind"
|
||||||
|
|
||||||
|
// to keep our CI/CD that checks go.mod and go.sum files happy, we need to import the package above
|
||||||
@@ -5,7 +5,6 @@ type PeerInfo struct {
|
|||||||
IP string
|
IP string
|
||||||
FQDN string
|
FQDN string
|
||||||
ConnStatus string // Todo replace to enum
|
ConnStatus string // Todo replace to enum
|
||||||
Direct bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerInfoCollection made for Java layer to get non default types as collection
|
// PeerInfoCollection made for Java layer to get non default types as collection
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
fw "github.com/netbirdio/netbird/client/firewall"
|
fw "github.com/netbirdio/netbird/client/firewall"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,12 +22,6 @@ const (
|
|||||||
ChainOutputFilterName = "NETBIRD-ACL-OUTPUT"
|
ChainOutputFilterName = "NETBIRD-ACL-OUTPUT"
|
||||||
)
|
)
|
||||||
|
|
||||||
// jumpNetbirdInputDefaultRule always added by manager to the input chain for all trafic from the Netbird interface
|
|
||||||
var jumpNetbirdInputDefaultRule = []string{"-j", ChainInputFilterName}
|
|
||||||
|
|
||||||
// jumpNetbirdOutputDefaultRule always added by manager to the output chain for all trafic from the Netbird interface
|
|
||||||
var jumpNetbirdOutputDefaultRule = []string{"-j", ChainOutputFilterName}
|
|
||||||
|
|
||||||
// dropAllDefaultRule in the Netbird chain
|
// dropAllDefaultRule in the Netbird chain
|
||||||
var dropAllDefaultRule = []string{"-j", "DROP"}
|
var dropAllDefaultRule = []string{"-j", "DROP"}
|
||||||
|
|
||||||
@@ -37,13 +32,25 @@ type Manager struct {
|
|||||||
ipv4Client *iptables.IPTables
|
ipv4Client *iptables.IPTables
|
||||||
ipv6Client *iptables.IPTables
|
ipv6Client *iptables.IPTables
|
||||||
|
|
||||||
wgIfaceName string
|
inputDefaultRuleSpecs []string
|
||||||
|
outputDefaultRuleSpecs []string
|
||||||
|
wgIface iFaceMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// iFaceMapper defines subset methods of interface required for manager
|
||||||
|
type iFaceMapper interface {
|
||||||
|
Name() string
|
||||||
|
Address() iface.WGAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create iptables firewall manager
|
// Create iptables firewall manager
|
||||||
func Create(wgIfaceName string) (*Manager, error) {
|
func Create(wgIface iFaceMapper) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
wgIfaceName: wgIfaceName,
|
wgIface: wgIface,
|
||||||
|
inputDefaultRuleSpecs: []string{
|
||||||
|
"-i", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address().String()},
|
||||||
|
outputDefaultRuleSpecs: []string{
|
||||||
|
"-o", wgIface.Name(), "-j", ChainOutputFilterName, "-d", wgIface.Address().String()},
|
||||||
}
|
}
|
||||||
|
|
||||||
// init clients for booth ipv4 and ipv6
|
// init clients for booth ipv4 and ipv6
|
||||||
@@ -193,11 +200,10 @@ func (m *Manager) reset(client *iptables.IPTables, table string) error {
|
|||||||
return fmt.Errorf("failed to check if input chain exists: %w", err)
|
return fmt.Errorf("failed to check if input chain exists: %w", err)
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
specs := append([]string{"-i", m.wgIfaceName}, jumpNetbirdInputDefaultRule...)
|
if ok, err := client.Exists("filter", "INPUT", m.inputDefaultRuleSpecs...); err != nil {
|
||||||
if ok, err := client.Exists("filter", "INPUT", specs...); err != nil {
|
|
||||||
return err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
if err := client.Delete("filter", "INPUT", specs...); err != nil {
|
if err := client.Delete("filter", "INPUT", m.inputDefaultRuleSpecs...); err != nil {
|
||||||
log.WithError(err).Errorf("failed to delete default input rule: %v", err)
|
log.WithError(err).Errorf("failed to delete default input rule: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,11 +214,10 @@ func (m *Manager) reset(client *iptables.IPTables, table string) error {
|
|||||||
return fmt.Errorf("failed to check if output chain exists: %w", err)
|
return fmt.Errorf("failed to check if output chain exists: %w", err)
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
specs := append([]string{"-o", m.wgIfaceName}, jumpNetbirdOutputDefaultRule...)
|
if ok, err := client.Exists("filter", "OUTPUT", m.outputDefaultRuleSpecs...); err != nil {
|
||||||
if ok, err := client.Exists("filter", "OUTPUT", specs...); err != nil {
|
|
||||||
return err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
if err := client.Delete("filter", "OUTPUT", specs...); err != nil {
|
if err := client.Delete("filter", "OUTPUT", m.outputDefaultRuleSpecs...); err != nil {
|
||||||
log.WithError(err).Errorf("failed to delete default output rule: %v", err)
|
log.WithError(err).Errorf("failed to delete default output rule: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,8 +301,7 @@ func (m *Manager) client(ip net.IP) (*iptables.IPTables, error) {
|
|||||||
return nil, fmt.Errorf("failed to create default drop all in netbird input chain: %w", err)
|
return nil, fmt.Errorf("failed to create default drop all in netbird input chain: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
specs := append([]string{"-i", m.wgIfaceName}, jumpNetbirdInputDefaultRule...)
|
if err := client.AppendUnique("filter", "INPUT", m.inputDefaultRuleSpecs...); err != nil {
|
||||||
if err := client.AppendUnique("filter", "INPUT", specs...); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create input chain jump rule: %w", err)
|
return nil, fmt.Errorf("failed to create input chain jump rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,8 +321,7 @@ func (m *Manager) client(ip net.IP) (*iptables.IPTables, error) {
|
|||||||
return nil, fmt.Errorf("failed to create default drop all in netbird output chain: %w", err)
|
return nil, fmt.Errorf("failed to create default drop all in netbird output chain: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
specs := append([]string{"-o", m.wgIfaceName}, jumpNetbirdOutputDefaultRule...)
|
if err := client.AppendUnique("filter", "OUTPUT", m.outputDefaultRuleSpecs...); err != nil {
|
||||||
if err := client.AppendUnique("filter", "OUTPUT", specs...); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create output chain jump rule: %w", err)
|
return nil, fmt.Errorf("failed to create output chain jump rule: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,50 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
fw "github.com/netbirdio/netbird/client/firewall"
|
fw "github.com/netbirdio/netbird/client/firewall"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// iFaceMapper defines subset methods of interface required for manager
|
||||||
|
type iFaceMock struct {
|
||||||
|
NameFunc func() string
|
||||||
|
AddressFunc func() iface.WGAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iFaceMock) Name() string {
|
||||||
|
if i.NameFunc != nil {
|
||||||
|
return i.NameFunc()
|
||||||
|
}
|
||||||
|
panic("NameFunc is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iFaceMock) Address() iface.WGAddress {
|
||||||
|
if i.AddressFunc != nil {
|
||||||
|
return i.AddressFunc()
|
||||||
|
}
|
||||||
|
panic("AddressFunc is not set")
|
||||||
|
}
|
||||||
|
|
||||||
func TestIptablesManager(t *testing.T) {
|
func TestIptablesManager(t *testing.T) {
|
||||||
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("10.20.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("10.20.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// just check on the local interface
|
// just check on the local interface
|
||||||
manager, err := Create("lo")
|
manager, err := Create(mock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@@ -94,10 +130,25 @@ func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIptablesCreatePerformance(t *testing.T) {
|
func TestIptablesCreatePerformance(t *testing.T) {
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("10.20.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("10.20.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
||||||
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
||||||
// just check on the local interface
|
// just check on the local interface
|
||||||
manager, err := Create("lo")
|
manager, err := Create(mock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
fw "github.com/netbirdio/netbird/client/firewall"
|
fw "github.com/netbirdio/netbird/client/firewall"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -42,14 +43,20 @@ type Manager struct {
|
|||||||
filterInputChainIPv6 *nftables.Chain
|
filterInputChainIPv6 *nftables.Chain
|
||||||
filterOutputChainIPv6 *nftables.Chain
|
filterOutputChainIPv6 *nftables.Chain
|
||||||
|
|
||||||
wgIfaceName string
|
wgIface iFaceMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// iFaceMapper defines subset methods of interface required for manager
|
||||||
|
type iFaceMapper interface {
|
||||||
|
Name() string
|
||||||
|
Address() iface.WGAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create nftables firewall manager
|
// Create nftables firewall manager
|
||||||
func Create(wgIfaceName string) (*Manager, error) {
|
func Create(wgIface iFaceMapper) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
conn: &nftables.Conn{},
|
conn: &nftables.Conn{},
|
||||||
wgIfaceName: wgIfaceName,
|
wgIface: wgIface,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.Reset(); err != nil {
|
if err := m.Reset(); err != nil {
|
||||||
@@ -109,7 +116,7 @@ func (m *Manager) AddFiltering(
|
|||||||
&expr.Cmp{
|
&expr.Cmp{
|
||||||
Op: expr.CmpOpEq,
|
Op: expr.CmpOpEq,
|
||||||
Register: 1,
|
Register: 1,
|
||||||
Data: ifname(m.wgIfaceName),
|
Data: ifname(m.wgIface.Name()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,15 +365,82 @@ func (m *Manager) createChainIfNotExists(
|
|||||||
chain = m.conn.AddChain(chain)
|
chain = m.conn.AddChain(chain)
|
||||||
|
|
||||||
ifaceKey := expr.MetaKeyIIFNAME
|
ifaceKey := expr.MetaKeyIIFNAME
|
||||||
|
shiftDSTAddr := 0
|
||||||
if name == FilterOutputChainName {
|
if name == FilterOutputChainName {
|
||||||
ifaceKey = expr.MetaKeyOIFNAME
|
ifaceKey = expr.MetaKeyOIFNAME
|
||||||
|
shiftDSTAddr = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
expressions := []expr.Any{
|
expressions := []expr.Any{
|
||||||
&expr.Meta{Key: ifaceKey, Register: 1},
|
&expr.Meta{Key: ifaceKey, Register: 1},
|
||||||
&expr.Cmp{
|
&expr.Cmp{
|
||||||
Op: expr.CmpOpEq,
|
Op: expr.CmpOpEq,
|
||||||
Register: 1,
|
Register: 1,
|
||||||
Data: ifname(m.wgIfaceName),
|
Data: ifname(m.wgIface.Name()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mask, _ := netip.AddrFromSlice(m.wgIface.Address().Network.Mask)
|
||||||
|
if m.wgIface.Address().IP.To4() == nil {
|
||||||
|
ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To16())
|
||||||
|
expressions = append(expressions,
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 2,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: uint32(8 + (16 * shiftDSTAddr)),
|
||||||
|
Len: 16,
|
||||||
|
},
|
||||||
|
&expr.Bitwise{
|
||||||
|
SourceRegister: 2,
|
||||||
|
DestRegister: 2,
|
||||||
|
Len: 16,
|
||||||
|
Xor: []byte{0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: mask.Unmap().AsSlice(),
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpNeq,
|
||||||
|
Register: 2,
|
||||||
|
Data: ip.Unmap().AsSlice(),
|
||||||
|
},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictAccept},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4())
|
||||||
|
expressions = append(expressions,
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 2,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: uint32(12 + (4 * shiftDSTAddr)),
|
||||||
|
Len: 4,
|
||||||
|
},
|
||||||
|
&expr.Bitwise{
|
||||||
|
SourceRegister: 2,
|
||||||
|
DestRegister: 2,
|
||||||
|
Len: 4,
|
||||||
|
Xor: []byte{0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: m.wgIface.Address().Network.Mask,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpNeq,
|
||||||
|
Register: 2,
|
||||||
|
Data: ip.Unmap().AsSlice(),
|
||||||
|
},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictAccept},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = m.conn.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: expressions,
|
||||||
|
})
|
||||||
|
|
||||||
|
expressions = []expr.Any{
|
||||||
|
&expr.Meta{Key: ifaceKey, Register: 1},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: ifname(m.wgIface.Name()),
|
||||||
},
|
},
|
||||||
&expr.Verdict{Kind: expr.VerdictDrop},
|
&expr.Verdict{Kind: expr.VerdictDrop},
|
||||||
}
|
}
|
||||||
@@ -375,7 +449,6 @@ func (m *Manager) createChainIfNotExists(
|
|||||||
Chain: chain,
|
Chain: chain,
|
||||||
Exprs: expressions,
|
Exprs: expressions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := m.conn.Flush(); err != nil {
|
if err := m.conn.Flush(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,47 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
fw "github.com/netbirdio/netbird/client/firewall"
|
fw "github.com/netbirdio/netbird/client/firewall"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// iFaceMapper defines subset methods of interface required for manager
|
||||||
|
type iFaceMock struct {
|
||||||
|
NameFunc func() string
|
||||||
|
AddressFunc func() iface.WGAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iFaceMock) Name() string {
|
||||||
|
if i.NameFunc != nil {
|
||||||
|
return i.NameFunc()
|
||||||
|
}
|
||||||
|
panic("NameFunc is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iFaceMock) Address() iface.WGAddress {
|
||||||
|
if i.AddressFunc != nil {
|
||||||
|
return i.AddressFunc()
|
||||||
|
}
|
||||||
|
panic("AddressFunc is not set")
|
||||||
|
}
|
||||||
|
|
||||||
func TestNftablesManager(t *testing.T) {
|
func TestNftablesManager(t *testing.T) {
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("100.96.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("100.96.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// just check on the local interface
|
// just check on the local interface
|
||||||
manager, err := Create("lo")
|
manager, err := Create(mock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@@ -44,8 +80,11 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
|
|
||||||
rules, err := testClient.GetRules(manager.tableIPv4, manager.filterInputChainIPv4)
|
rules, err := testClient.GetRules(manager.tableIPv4, manager.filterInputChainIPv4)
|
||||||
require.NoError(t, err, "failed to get rules")
|
require.NoError(t, err, "failed to get rules")
|
||||||
// 1 regular rule and other "drop all rule" for the interface
|
// test expectations:
|
||||||
require.Len(t, rules, 2, "expected 1 rule")
|
// 1) regular rule
|
||||||
|
// 2) "accept extra routed traffic rule" for the interface
|
||||||
|
// 3) "drop all rule" for the interface
|
||||||
|
require.Len(t, rules, 3, "expected 3 rules")
|
||||||
|
|
||||||
ipToAdd, _ := netip.AddrFromSlice(ip)
|
ipToAdd, _ := netip.AddrFromSlice(ip)
|
||||||
add := ipToAdd.Unmap()
|
add := ipToAdd.Unmap()
|
||||||
@@ -98,17 +137,35 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
|
|
||||||
rules, err = testClient.GetRules(manager.tableIPv4, manager.filterInputChainIPv4)
|
rules, err = testClient.GetRules(manager.tableIPv4, manager.filterInputChainIPv4)
|
||||||
require.NoError(t, err, "failed to get rules")
|
require.NoError(t, err, "failed to get rules")
|
||||||
require.Len(t, rules, 1, "expected 1 rules after deleteion")
|
// test expectations:
|
||||||
|
// 1) "accept extra routed traffic rule" for the interface
|
||||||
|
// 2) "drop all rule" for the interface
|
||||||
|
require.Len(t, rules, 2, "expected 2 rules after deleteion")
|
||||||
|
|
||||||
err = manager.Reset()
|
err = manager.Reset()
|
||||||
require.NoError(t, err, "failed to reset")
|
require.NoError(t, err, "failed to reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNFtablesCreatePerformance(t *testing.T) {
|
func TestNFtablesCreatePerformance(t *testing.T) {
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("100.96.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("100.96.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
||||||
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
||||||
// just check on the local interface
|
// just check on the local interface
|
||||||
manager, err := Create("lo")
|
manager, err := Create(mock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
// Protocol is the protocol of the port
|
// Protocol is the protocol of the port
|
||||||
type Protocol string
|
type Protocol string
|
||||||
|
|
||||||
@@ -28,3 +32,15 @@ type Port struct {
|
|||||||
// Values contains one value for single port, multiple values for the list of ports, or two values for the range of ports
|
// Values contains one value for single port, multiple values for the list of ports, or two values for the range of ports
|
||||||
Values []int
|
Values []int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String interface implementation
|
||||||
|
func (p *Port) String() string {
|
||||||
|
var ports string
|
||||||
|
for _, port := range p.Values {
|
||||||
|
if ports != "" {
|
||||||
|
ports += ","
|
||||||
|
}
|
||||||
|
ports += strconv.Itoa(port)
|
||||||
|
}
|
||||||
|
return ports
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ type Rule struct {
|
|||||||
dPort uint16
|
dPort uint16
|
||||||
drop bool
|
drop bool
|
||||||
comment string
|
comment string
|
||||||
|
|
||||||
|
udpHook func([]byte) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRuleID returns the rule id
|
// GetRuleID returns the rule id
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const layerTypeAll = 0
|
|||||||
|
|
||||||
// IFaceMapper defines subset methods of interface required for manager
|
// IFaceMapper defines subset methods of interface required for manager
|
||||||
type IFaceMapper interface {
|
type IFaceMapper interface {
|
||||||
SetFiltering(iface.PacketFilter) error
|
SetFilter(iface.PacketFilter) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager userspace firewall manager
|
// Manager userspace firewall manager
|
||||||
@@ -64,7 +64,7 @@ func Create(iface IFaceMapper) (*Manager, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := iface.SetFiltering(m); err != nil {
|
if err := iface.SetFilter(m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -273,6 +273,12 @@ func (m *Manager) dropFilter(packetData []byte, rules []Rule, isIncomingPacket b
|
|||||||
return rule.drop
|
return rule.drop
|
||||||
}
|
}
|
||||||
case layers.LayerTypeUDP:
|
case layers.LayerTypeUDP:
|
||||||
|
// if rule has UDP hook (and if we are here we match this rule)
|
||||||
|
// we ignore rule.drop and call this hook
|
||||||
|
if rule.udpHook != nil {
|
||||||
|
return rule.udpHook(packetData)
|
||||||
|
}
|
||||||
|
|
||||||
if rule.sPort == 0 && rule.dPort == 0 {
|
if rule.sPort == 0 && rule.dPort == 0 {
|
||||||
return rule.drop
|
return rule.drop
|
||||||
}
|
}
|
||||||
@@ -296,3 +302,58 @@ func (m *Manager) dropFilter(packetData []byte, rules []Rule, isIncomingPacket b
|
|||||||
func (m *Manager) SetNetwork(network *net.IPNet) {
|
func (m *Manager) SetNetwork(network *net.IPNet) {
|
||||||
m.wgNetwork = network
|
m.wgNetwork = network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddUDPPacketHook calls hook when UDP packet from given direction matched
|
||||||
|
//
|
||||||
|
// Hook function returns flag which indicates should be the matched package dropped or not
|
||||||
|
func (m *Manager) AddUDPPacketHook(
|
||||||
|
in bool, ip net.IP, dPort uint16, hook func([]byte) bool,
|
||||||
|
) string {
|
||||||
|
r := Rule{
|
||||||
|
id: uuid.New().String(),
|
||||||
|
ip: ip,
|
||||||
|
protoLayer: layers.LayerTypeUDP,
|
||||||
|
dPort: dPort,
|
||||||
|
ipLayer: layers.LayerTypeIPv6,
|
||||||
|
direction: fw.RuleDirectionOUT,
|
||||||
|
comment: fmt.Sprintf("UDP Hook direction: %v, ip:%v, dport:%d", in, ip, dPort),
|
||||||
|
udpHook: hook,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.To4() != nil {
|
||||||
|
r.ipLayer = layers.LayerTypeIPv4
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mutex.Lock()
|
||||||
|
var toUpdate []Rule
|
||||||
|
if in {
|
||||||
|
r.direction = fw.RuleDirectionIN
|
||||||
|
m.incomingRules = append([]Rule{r}, m.incomingRules...)
|
||||||
|
toUpdate = m.incomingRules
|
||||||
|
} else {
|
||||||
|
m.outgoingRules = append([]Rule{r}, m.outgoingRules...)
|
||||||
|
toUpdate = m.outgoingRules
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range toUpdate {
|
||||||
|
m.rulesIndex[toUpdate[i].id] = i
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
|
||||||
|
return r.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePacketHook removes packet hook by given ID
|
||||||
|
func (m *Manager) RemovePacketHook(hookID string) error {
|
||||||
|
for _, r := range m.incomingRules {
|
||||||
|
if r.id == hookID {
|
||||||
|
return m.DeleteRule(&r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range m.outgoingRules {
|
||||||
|
if r.id == hookID {
|
||||||
|
return m.DeleteRule(&r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("hook with given id not found")
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IFaceMock struct {
|
type IFaceMock struct {
|
||||||
SetFilteringFunc func(iface.PacketFilter) error
|
SetFilterFunc func(iface.PacketFilter) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IFaceMock) SetFiltering(iface iface.PacketFilter) error {
|
func (i *IFaceMock) SetFilter(iface iface.PacketFilter) error {
|
||||||
if i.SetFilteringFunc == nil {
|
if i.SetFilterFunc == nil {
|
||||||
return fmt.Errorf("not implemented")
|
return fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
return i.SetFilteringFunc(iface)
|
return i.SetFilterFunc(iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManagerCreate(t *testing.T) {
|
func TestManagerCreate(t *testing.T) {
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := Create(ifaceMock)
|
m, err := Create(ifaceMock)
|
||||||
@@ -42,10 +42,10 @@ func TestManagerCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManagerAddFiltering(t *testing.T) {
|
func TestManagerAddFiltering(t *testing.T) {
|
||||||
isSetFilteringCalled := false
|
isSetFilterCalled := false
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilteringFunc: func(iface.PacketFilter) error {
|
SetFilterFunc: func(iface.PacketFilter) error {
|
||||||
isSetFilteringCalled = true
|
isSetFilterCalled = true
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -74,15 +74,15 @@ func TestManagerAddFiltering(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isSetFilteringCalled {
|
if !isSetFilterCalled {
|
||||||
t.Error("SetFiltering was not called")
|
t.Error("SetFilter was not called")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManagerDeleteRule(t *testing.T) {
|
func TestManagerDeleteRule(t *testing.T) {
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := Create(ifaceMock)
|
m, err := Create(ifaceMock)
|
||||||
@@ -138,9 +138,97 @@ func TestManagerDeleteRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddUDPPacketHook(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in bool
|
||||||
|
expDir fw.RuleDirection
|
||||||
|
ip net.IP
|
||||||
|
dPort uint16
|
||||||
|
hook func([]byte) bool
|
||||||
|
expectedID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test Outgoing UDP Packet Hook",
|
||||||
|
in: false,
|
||||||
|
expDir: fw.RuleDirectionOUT,
|
||||||
|
ip: net.IPv4(10, 168, 0, 1),
|
||||||
|
dPort: 8000,
|
||||||
|
hook: func([]byte) bool { return true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Incoming UDP Packet Hook",
|
||||||
|
in: true,
|
||||||
|
expDir: fw.RuleDirectionIN,
|
||||||
|
ip: net.IPv6loopback,
|
||||||
|
dPort: 9000,
|
||||||
|
hook: func([]byte) bool { return false },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
manager := &Manager{
|
||||||
|
incomingRules: []Rule{},
|
||||||
|
outgoingRules: []Rule{},
|
||||||
|
rulesIndex: make(map[string]int),
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.AddUDPPacketHook(tt.in, tt.ip, tt.dPort, tt.hook)
|
||||||
|
|
||||||
|
var addedRule Rule
|
||||||
|
if tt.in {
|
||||||
|
if len(manager.incomingRules) != 1 {
|
||||||
|
t.Errorf("expected 1 incoming rule, got %d", len(manager.incomingRules))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addedRule = manager.incomingRules[0]
|
||||||
|
} else {
|
||||||
|
if len(manager.outgoingRules) != 1 {
|
||||||
|
t.Errorf("expected 1 outgoing rule, got %d", len(manager.outgoingRules))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addedRule = manager.outgoingRules[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.ip.Equal(addedRule.ip) {
|
||||||
|
t.Errorf("expected ip %s, got %s", tt.ip, addedRule.ip)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.dPort != addedRule.dPort {
|
||||||
|
t.Errorf("expected dPort %d, got %d", tt.dPort, addedRule.dPort)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if layers.LayerTypeUDP != addedRule.protoLayer {
|
||||||
|
t.Errorf("expected protoLayer %s, got %s", layers.LayerTypeUDP, addedRule.protoLayer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.expDir != addedRule.direction {
|
||||||
|
t.Errorf("expected direction %d, got %d", tt.expDir, addedRule.direction)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addedRule.udpHook == nil {
|
||||||
|
t.Errorf("expected udpHook to be set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure rulesIndex is correctly updated
|
||||||
|
index, ok := manager.rulesIndex[addedRule.id]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected rule to be in rulesIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if index != 0 {
|
||||||
|
t.Errorf("expected rule index to be 0, got %d", index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestManagerReset(t *testing.T) {
|
func TestManagerReset(t *testing.T) {
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := Create(ifaceMock)
|
m, err := Create(ifaceMock)
|
||||||
@@ -175,7 +263,7 @@ func TestManagerReset(t *testing.T) {
|
|||||||
|
|
||||||
func TestNotMatchByIP(t *testing.T) {
|
func TestNotMatchByIP(t *testing.T) {
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := Create(ifaceMock)
|
m, err := Create(ifaceMock)
|
||||||
@@ -239,12 +327,56 @@ func TestNotMatchByIP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRemovePacketHook tests the functionality of the RemovePacketHook method
|
||||||
|
func TestRemovePacketHook(t *testing.T) {
|
||||||
|
// creating mock iface
|
||||||
|
iface := &IFaceMock{
|
||||||
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating manager instance
|
||||||
|
manager, err := Create(iface)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create Manager: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a UDP packet hook
|
||||||
|
hookFunc := func(data []byte) bool { return true }
|
||||||
|
hookID := manager.AddUDPPacketHook(false, net.IPv4(192, 168, 0, 1), 8080, hookFunc)
|
||||||
|
|
||||||
|
// Assert the hook is added by finding it in the manager's outgoing rules
|
||||||
|
found := false
|
||||||
|
for _, rule := range manager.outgoingRules {
|
||||||
|
if rule.id == hookID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("The hook was not added properly.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now remove the packet hook
|
||||||
|
err = manager.RemovePacketHook(hookID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove hook: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert the hook is removed by checking it in the manager's outgoing rules
|
||||||
|
for _, rule := range manager.outgoingRules {
|
||||||
|
if rule.id == hookID {
|
||||||
|
t.Fatalf("The hook was not removed properly.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUSPFilterCreatePerformance(t *testing.T) {
|
func TestUSPFilterCreatePerformance(t *testing.T) {
|
||||||
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
||||||
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
||||||
// just check on the local interface
|
// just check on the local interface
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
}
|
}
|
||||||
manager, err := Create(ifaceMock)
|
manager, err := Create(ifaceMock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package acl
|
package acl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
@@ -14,11 +17,12 @@ import (
|
|||||||
mgmProto "github.com/netbirdio/netbird/management/proto"
|
mgmProto "github.com/netbirdio/netbird/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// iFaceMapper defines subset methods of interface required for manager
|
// IFaceMapper defines subset methods of interface required for manager
|
||||||
type iFaceMapper interface {
|
type IFaceMapper interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
Address() iface.WGAddress
|
||||||
IsUserspaceBind() bool
|
IsUserspaceBind() bool
|
||||||
SetFiltering(iface.PacketFilter) error
|
SetFilter(iface.PacketFilter) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager is a ACL rules manager
|
// Manager is a ACL rules manager
|
||||||
@@ -41,6 +45,17 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
d.mutex.Lock()
|
d.mutex.Lock()
|
||||||
defer d.mutex.Unlock()
|
defer d.mutex.Unlock()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
total := 0
|
||||||
|
for _, pairs := range d.rulesPairs {
|
||||||
|
total += len(pairs)
|
||||||
|
}
|
||||||
|
log.Infof(
|
||||||
|
"ACL rules processed in: %v, total rules count: %d",
|
||||||
|
time.Since(start), total)
|
||||||
|
}()
|
||||||
|
|
||||||
if d.manager == nil {
|
if d.manager == nil {
|
||||||
log.Debug("firewall manager is not supported, skipping firewall rules")
|
log.Debug("firewall manager is not supported, skipping firewall rules")
|
||||||
return
|
return
|
||||||
@@ -94,13 +109,13 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
applyFailed := false
|
applyFailed := false
|
||||||
newRulePairs := make(map[string][]firewall.Rule)
|
newRulePairs := make(map[string][]firewall.Rule)
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
rulePair, err := d.protoRuleToFirewallRule(r)
|
pairID, rulePair, err := d.protoRuleToFirewallRule(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to apply firewall rule: %+v, %v", r, err)
|
log.Errorf("failed to apply firewall rule: %+v, %v", r, err)
|
||||||
applyFailed = true
|
applyFailed = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
newRulePairs[rulePair[0].GetRuleID()] = rulePair
|
newRulePairs[pairID] = rulePair
|
||||||
}
|
}
|
||||||
if applyFailed {
|
if applyFailed {
|
||||||
log.Error("failed to apply firewall rules, rollback ACL to previous state")
|
log.Error("failed to apply firewall rules, rollback ACL to previous state")
|
||||||
@@ -139,33 +154,38 @@ func (d *DefaultManager) Stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultManager) protoRuleToFirewallRule(r *mgmProto.FirewallRule) ([]firewall.Rule, error) {
|
func (d *DefaultManager) protoRuleToFirewallRule(r *mgmProto.FirewallRule) (string, []firewall.Rule, error) {
|
||||||
ip := net.ParseIP(r.PeerIP)
|
ip := net.ParseIP(r.PeerIP)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, fmt.Errorf("invalid IP address, skipping firewall rule")
|
return "", nil, fmt.Errorf("invalid IP address, skipping firewall rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol := convertToFirewallProtocol(r.Protocol)
|
protocol := convertToFirewallProtocol(r.Protocol)
|
||||||
if protocol == firewall.ProtocolUnknown {
|
if protocol == firewall.ProtocolUnknown {
|
||||||
return nil, fmt.Errorf("invalid protocol type: %d, skipping firewall rule", r.Protocol)
|
return "", nil, fmt.Errorf("invalid protocol type: %d, skipping firewall rule", r.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
action := convertFirewallAction(r.Action)
|
action := convertFirewallAction(r.Action)
|
||||||
if action == firewall.ActionUnknown {
|
if action == firewall.ActionUnknown {
|
||||||
return nil, fmt.Errorf("invalid action type: %d, skipping firewall rule", r.Action)
|
return "", nil, fmt.Errorf("invalid action type: %d, skipping firewall rule", r.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
var port *firewall.Port
|
var port *firewall.Port
|
||||||
if r.Port != "" {
|
if r.Port != "" {
|
||||||
value, err := strconv.Atoi(r.Port)
|
value, err := strconv.Atoi(r.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid port, skipping firewall rule")
|
return "", nil, fmt.Errorf("invalid port, skipping firewall rule")
|
||||||
}
|
}
|
||||||
port = &firewall.Port{
|
port = &firewall.Port{
|
||||||
Values: []int{value},
|
Values: []int{value},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID := d.getRuleID(ip, protocol, int(r.Direction), port, action, "")
|
||||||
|
if rulesPair, ok := d.rulesPairs[ruleID]; ok {
|
||||||
|
return ruleID, rulesPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
var rules []firewall.Rule
|
var rules []firewall.Rule
|
||||||
var err error
|
var err error
|
||||||
switch r.Direction {
|
switch r.Direction {
|
||||||
@@ -174,15 +194,15 @@ func (d *DefaultManager) protoRuleToFirewallRule(r *mgmProto.FirewallRule) ([]fi
|
|||||||
case mgmProto.FirewallRule_OUT:
|
case mgmProto.FirewallRule_OUT:
|
||||||
rules, err = d.addOutRules(ip, protocol, port, action, "")
|
rules, err = d.addOutRules(ip, protocol, port, action, "")
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid direction, skipping firewall rule")
|
return "", nil, fmt.Errorf("invalid direction, skipping firewall rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.rulesPairs[rules[0].GetRuleID()] = rules
|
d.rulesPairs[ruleID] = rules
|
||||||
return rules, nil
|
return ruleID, rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultManager) addInRules(ip net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, comment string) ([]firewall.Rule, error) {
|
func (d *DefaultManager) addInRules(ip net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, comment string) ([]firewall.Rule, error) {
|
||||||
@@ -225,6 +245,23 @@ func (d *DefaultManager) addOutRules(ip net.IP, protocol firewall.Protocol, port
|
|||||||
return append(rules, rule), nil
|
return append(rules, rule), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRuleID() returns unique ID for the rule based on its parameters.
|
||||||
|
func (d *DefaultManager) getRuleID(
|
||||||
|
ip net.IP,
|
||||||
|
proto firewall.Protocol,
|
||||||
|
direction int,
|
||||||
|
port *firewall.Port,
|
||||||
|
action firewall.Action,
|
||||||
|
comment string,
|
||||||
|
) string {
|
||||||
|
idStr := ip.String() + string(proto) + strconv.Itoa(direction) + strconv.Itoa(int(action)) + comment
|
||||||
|
if port != nil {
|
||||||
|
idStr += port.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(md5.New().Sum([]byte(idStr)))
|
||||||
|
}
|
||||||
|
|
||||||
// squashAcceptRules does complex logic to convert many rules which allows connection by traffic type
|
// squashAcceptRules does complex logic to convert many rules which allows connection by traffic type
|
||||||
// to all peers in the network map to one rule which just accepts that type of the traffic.
|
// to all peers in the network map to one rule which just accepts that type of the traffic.
|
||||||
//
|
//
|
||||||
@@ -234,7 +271,7 @@ func (d *DefaultManager) squashAcceptRules(
|
|||||||
networkMap *mgmProto.NetworkMap,
|
networkMap *mgmProto.NetworkMap,
|
||||||
) ([]*mgmProto.FirewallRule, map[mgmProto.FirewallRuleProtocol]struct{}) {
|
) ([]*mgmProto.FirewallRule, map[mgmProto.FirewallRuleProtocol]struct{}) {
|
||||||
totalIPs := 0
|
totalIPs := 0
|
||||||
for _, p := range networkMap.RemotePeers {
|
for _, p := range append(networkMap.RemotePeers, networkMap.OfflinePeers...) {
|
||||||
for range p.AllowedIps {
|
for range p.AllowedIps {
|
||||||
totalIPs++
|
totalIPs++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create creates a firewall manager instance
|
// Create creates a firewall manager instance
|
||||||
func Create(iface iFaceMapper) (manager *DefaultManager, err error) {
|
func Create(iface IFaceMapper) (manager *DefaultManager, err error) {
|
||||||
if iface.IsUserspaceBind() {
|
if iface.IsUserspaceBind() {
|
||||||
// use userspace packet filtering firewall
|
// use userspace packet filtering firewall
|
||||||
fm, err := uspfilter.Create(iface)
|
fm, err := uspfilter.Create(iface)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create creates a firewall manager instance for the Linux
|
// Create creates a firewall manager instance for the Linux
|
||||||
func Create(iface iFaceMapper) (manager *DefaultManager, err error) {
|
func Create(iface IFaceMapper) (manager *DefaultManager, err error) {
|
||||||
var fm firewall.Manager
|
var fm firewall.Manager
|
||||||
if iface.IsUserspaceBind() {
|
if iface.IsUserspaceBind() {
|
||||||
// use userspace packet filtering firewall
|
// use userspace packet filtering firewall
|
||||||
@@ -19,10 +19,10 @@ func Create(iface iFaceMapper) (manager *DefaultManager, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fm, err = nftables.Create(iface.Name()); err != nil {
|
if fm, err = nftables.Create(iface); err != nil {
|
||||||
log.Debugf("failed to create nftables manager: %s", err)
|
log.Debugf("failed to create nftables manager: %s", err)
|
||||||
// fallback to iptables
|
// fallback to iptables
|
||||||
if fm, err = iptables.Create(iface.Name()); err != nil {
|
if fm, err = iptables.Create(iface); err != nil {
|
||||||
log.Errorf("failed to create iptables manager: %s", err)
|
log.Errorf("failed to create iptables manager: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
iface := mocks.NewMockIFaceMapper(ctrl)
|
iface := mocks.NewMockIFaceMapper(ctrl)
|
||||||
iface.EXPECT().IsUserspaceBind().Return(true)
|
iface.EXPECT().IsUserspaceBind().Return(true)
|
||||||
// iface.EXPECT().Name().Return("lo")
|
// iface.EXPECT().Name().Return("lo")
|
||||||
iface.EXPECT().SetFiltering(gomock.Any())
|
iface.EXPECT().SetFilter(gomock.Any())
|
||||||
|
|
||||||
// we receive one rule from the management so for testing purposes ignore it
|
// we receive one rule from the management so for testing purposes ignore it
|
||||||
acl, err := Create(iface)
|
acl, err := Create(iface)
|
||||||
@@ -55,6 +55,11 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("add extra rules", func(t *testing.T) {
|
t.Run("add extra rules", func(t *testing.T) {
|
||||||
|
existedPairs := map[string]struct{}{}
|
||||||
|
for id := range acl.rulesPairs {
|
||||||
|
existedPairs[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// remove first rule
|
// remove first rule
|
||||||
networkMap.FirewallRules = networkMap.FirewallRules[1:]
|
networkMap.FirewallRules = networkMap.FirewallRules[1:]
|
||||||
networkMap.FirewallRules = append(
|
networkMap.FirewallRules = append(
|
||||||
@@ -67,11 +72,6 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
existedRulesID := map[string]struct{}{}
|
|
||||||
for id := range acl.rulesPairs {
|
|
||||||
existedRulesID[id] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
acl.ApplyFiltering(networkMap)
|
acl.ApplyFiltering(networkMap)
|
||||||
|
|
||||||
// we should have one old and one new rule in the existed rules
|
// we should have one old and one new rule in the existed rules
|
||||||
@@ -80,13 +80,16 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that old rules was removed
|
// check that old rule was removed
|
||||||
for id := range existedRulesID {
|
previousCount := 0
|
||||||
if _, ok := acl.rulesPairs[id]; ok {
|
for id := range acl.rulesPairs {
|
||||||
t.Errorf("old rule was not removed")
|
if _, ok := existedPairs[id]; ok {
|
||||||
return
|
previousCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if previousCount != 1 {
|
||||||
|
t.Errorf("old rule was not removed")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle default rules", func(t *testing.T) {
|
t.Run("handle default rules", func(t *testing.T) {
|
||||||
@@ -311,7 +314,7 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) {
|
|||||||
iface := mocks.NewMockIFaceMapper(ctrl)
|
iface := mocks.NewMockIFaceMapper(ctrl)
|
||||||
iface.EXPECT().IsUserspaceBind().Return(true)
|
iface.EXPECT().IsUserspaceBind().Return(true)
|
||||||
// iface.EXPECT().Name().Return("lo")
|
// iface.EXPECT().Name().Return("lo")
|
||||||
iface.EXPECT().SetFiltering(gomock.Any())
|
iface.EXPECT().SetFilter(gomock.Any())
|
||||||
|
|
||||||
// we receive one rule from the management so for testing purposes ignore it
|
// we receive one rule from the management so for testing purposes ignore it
|
||||||
acl, err := Create(iface)
|
acl, err := Create(iface)
|
||||||
|
|||||||
7
client/internal/acl/mocks/README.md
Normal file
7
client/internal/acl/mocks/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Mocks
|
||||||
|
|
||||||
|
To generate (or refresh) mocks from acl package please install [mockgen](https://github.com/golang/mock).
|
||||||
|
Run this command from the `./client/internal/acl` folder to update iface mapper interface mock:
|
||||||
|
```bash
|
||||||
|
mockgen -destination mocks/iface_mapper.go -package mocks . IFaceMapper
|
||||||
|
```
|
||||||
@@ -34,6 +34,20 @@ func (m *MockIFaceMapper) EXPECT() *MockIFaceMapperMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Address mocks base method.
|
||||||
|
func (m *MockIFaceMapper) Address() iface.WGAddress {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Address")
|
||||||
|
ret0, _ := ret[0].(iface.WGAddress)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address indicates an expected call of Address.
|
||||||
|
func (mr *MockIFaceMapperMockRecorder) Address() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockIFaceMapper)(nil).Address))
|
||||||
|
}
|
||||||
|
|
||||||
// IsUserspaceBind mocks base method.
|
// IsUserspaceBind mocks base method.
|
||||||
func (m *MockIFaceMapper) IsUserspaceBind() bool {
|
func (m *MockIFaceMapper) IsUserspaceBind() bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -62,16 +76,16 @@ func (mr *MockIFaceMapperMockRecorder) Name() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockIFaceMapper)(nil).Name))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockIFaceMapper)(nil).Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFiltering mocks base method.
|
// SetFilter mocks base method.
|
||||||
func (m *MockIFaceMapper) SetFiltering(arg0 iface.PacketFilter) error {
|
func (m *MockIFaceMapper) SetFilter(arg0 iface.PacketFilter) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetFiltering", arg0)
|
ret := m.ctrl.Call(m, "SetFilter", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFiltering indicates an expected call of SetFiltering.
|
// SetFilter indicates an expected call of SetFilter.
|
||||||
func (mr *MockIFaceMapperMockRecorder) SetFiltering(arg0 interface{}) *gomock.Call {
|
func (mr *MockIFaceMapperMockRecorder) SetFilter(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFiltering", reflect.TypeOf((*MockIFaceMapper)(nil).SetFiltering), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFilter", reflect.TypeOf((*MockIFaceMapper)(nil).SetFilter), arg0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
24
client/internal/dns/host_android.go
Normal file
24
client/internal/dns/host_android.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
)
|
||||||
|
|
||||||
|
type androidHostManager struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostManager(wgInterface *iface.WGIface) (hostManager, error) {
|
||||||
|
return &androidHostManager{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a androidHostManager) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a androidHostManager) restoreHostDNS() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a androidHostManager) supportCustomPort() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func (d *localResolver) stop() {
|
|||||||
|
|
||||||
// ServeDNS handles a DNS request
|
// ServeDNS handles a DNS request
|
||||||
func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
log.Tracef("received question: %#v\n", r.Question[0])
|
log.Tracef("received question: %#v", r.Question[0])
|
||||||
replyMessage := &dns.Msg{}
|
replyMessage := &dns.Msg{}
|
||||||
replyMessage.SetReply(r)
|
replyMessage.SetReply(r)
|
||||||
replyMessage.RecursionAvailable = true
|
replyMessage.RecursionAvailable = true
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ import (
|
|||||||
|
|
||||||
// MockServer is the mock instance of a dns server
|
// MockServer is the mock instance of a dns server
|
||||||
type MockServer struct {
|
type MockServer struct {
|
||||||
StartFunc func()
|
InitializeFunc func() error
|
||||||
StopFunc func()
|
StopFunc func()
|
||||||
UpdateDNSServerFunc func(serial uint64, update nbdns.Config) error
|
UpdateDNSServerFunc func(serial uint64, update nbdns.Config) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start mock implementation of Start from Server interface
|
// Initialize mock implementation of Initialize from Server interface
|
||||||
func (m *MockServer) Start() {
|
func (m *MockServer) Initialize() error {
|
||||||
if m.StartFunc != nil {
|
if m.InitializeFunc != nil {
|
||||||
m.StartFunc()
|
return m.InitializeFunc()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop mock implementation of Stop from Server interface
|
// Stop mock implementation of Stop from Server interface
|
||||||
@@ -26,6 +27,10 @@ func (m *MockServer) Stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockServer) DnsIP() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateDNSServer mock implementation of UpdateDNSServer from Server interface
|
// UpdateDNSServer mock implementation of UpdateDNSServer from Server interface
|
||||||
func (m *MockServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
func (m *MockServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
||||||
if m.UpdateDNSServerFunc != nil {
|
if m.UpdateDNSServerFunc != nil {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
103
client/internal/dns/response_writer.go
Normal file
103
client/internal/dns/response_writer.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
local net.Addr
|
||||||
|
remote net.Addr
|
||||||
|
packet gopacket.Packet
|
||||||
|
device tun.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the net.Addr of the server
|
||||||
|
func (r *responseWriter) LocalAddr() net.Addr {
|
||||||
|
return r.local
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the net.Addr of the client that sent the current request.
|
||||||
|
func (r *responseWriter) RemoteAddr() net.Addr {
|
||||||
|
return r.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg writes a reply back to the client.
|
||||||
|
func (r *responseWriter) WriteMsg(msg *dns.Msg) error {
|
||||||
|
buff, err := msg.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = r.Write(buff)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes a raw buffer back to the client.
|
||||||
|
func (r *responseWriter) Write(data []byte) (int, error) {
|
||||||
|
var ip gopacket.SerializableLayer
|
||||||
|
|
||||||
|
// Get the UDP layer
|
||||||
|
udpLayer := r.packet.Layer(layers.LayerTypeUDP)
|
||||||
|
udp := udpLayer.(*layers.UDP)
|
||||||
|
|
||||||
|
// Swap the source and destination addresses for the response
|
||||||
|
udp.SrcPort, udp.DstPort = udp.DstPort, udp.SrcPort
|
||||||
|
|
||||||
|
// Check if it's an IPv4 packet
|
||||||
|
if ipv4Layer := r.packet.Layer(layers.LayerTypeIPv4); ipv4Layer != nil {
|
||||||
|
ipv4 := ipv4Layer.(*layers.IPv4)
|
||||||
|
ipv4.SrcIP, ipv4.DstIP = ipv4.DstIP, ipv4.SrcIP
|
||||||
|
ip = ipv4
|
||||||
|
} else if ipv6Layer := r.packet.Layer(layers.LayerTypeIPv6); ipv6Layer != nil {
|
||||||
|
ipv6 := ipv6Layer.(*layers.IPv6)
|
||||||
|
ipv6.SrcIP, ipv6.DstIP = ipv6.DstIP, ipv6.SrcIP
|
||||||
|
ip = ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := udp.SetNetworkLayerForChecksum(ip.(gopacket.NetworkLayer)); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to set network layer for checksum: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the packet
|
||||||
|
buffer := gopacket.NewSerializeBuffer()
|
||||||
|
options := gopacket.SerializeOptions{
|
||||||
|
ComputeChecksums: true,
|
||||||
|
FixLengths: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := gopacket.Payload(data)
|
||||||
|
err := gopacket.SerializeLayers(buffer, options, ip, udp, payload)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to serialize packet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
send := buffer.Bytes()
|
||||||
|
sendBuffer := make([]byte, 40, len(send)+40)
|
||||||
|
sendBuffer = append(sendBuffer, send...)
|
||||||
|
|
||||||
|
return r.device.Write([][]byte{sendBuffer}, 40)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (r *responseWriter) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TsigStatus returns the status of the Tsig.
|
||||||
|
func (r *responseWriter) TsigStatus() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TsigTimersOnly sets the tsig timers only boolean.
|
||||||
|
func (r *responseWriter) TsigTimersOnly(bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack lets the caller take over the connection.
|
||||||
|
// After a call to Hijack(), the DNS package will not do anything with the connection.
|
||||||
|
func (r *responseWriter) Hijack() {
|
||||||
|
}
|
||||||
93
client/internal/dns/response_writer_test.go
Normal file
93
client/internal/dns/response_writer_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/iface/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResponseWriterLocalAddr(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
device := mocks.NewMockDevice(ctrl)
|
||||||
|
device.EXPECT().Write(gomock.Any(), gomock.Any())
|
||||||
|
|
||||||
|
request := &dns.Msg{
|
||||||
|
Question: []dns.Question{{
|
||||||
|
Name: "google.com.",
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Qclass: dns.TypeA,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
replyMessage := &dns.Msg{}
|
||||||
|
replyMessage.SetReply(request)
|
||||||
|
replyMessage.RecursionAvailable = true
|
||||||
|
replyMessage.Rcode = dns.RcodeSuccess
|
||||||
|
replyMessage.Answer = []dns.RR{
|
||||||
|
&dns.A{
|
||||||
|
A: net.IPv4(8, 8, 8, 8),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4 := &layers.IPv4{
|
||||||
|
Protocol: layers.IPProtocolUDP,
|
||||||
|
SrcIP: net.IPv4(127, 0, 0, 1),
|
||||||
|
DstIP: net.IPv4(127, 0, 0, 2),
|
||||||
|
}
|
||||||
|
udp := &layers.UDP{
|
||||||
|
DstPort: 53,
|
||||||
|
SrcPort: 45223,
|
||||||
|
}
|
||||||
|
if err := udp.SetNetworkLayerForChecksum(ipv4); err != nil {
|
||||||
|
t.Error("failed to set network layer for checksum")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the packet
|
||||||
|
buffer := gopacket.NewSerializeBuffer()
|
||||||
|
options := gopacket.SerializeOptions{
|
||||||
|
ComputeChecksums: true,
|
||||||
|
FixLengths: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
requestData, err := request.Pack()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("got an error while packing the request message, error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload := gopacket.Payload(requestData)
|
||||||
|
|
||||||
|
if err := gopacket.SerializeLayers(buffer, options, ipv4, udp, payload); err != nil {
|
||||||
|
t.Errorf("failed to serialize packet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw := &responseWriter{
|
||||||
|
local: &net.UDPAddr{
|
||||||
|
IP: net.IPv4(127, 0, 0, 1),
|
||||||
|
Port: 55223,
|
||||||
|
},
|
||||||
|
remote: &net.UDPAddr{
|
||||||
|
IP: net.IPv4(127, 0, 0, 1),
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
packet: gopacket.NewPacket(
|
||||||
|
buffer.Bytes(),
|
||||||
|
layers.LayerTypeIPv4,
|
||||||
|
gopacket.Default,
|
||||||
|
),
|
||||||
|
device: device,
|
||||||
|
}
|
||||||
|
if err := rw.WriteMsg(replyMessage); err != nil {
|
||||||
|
t.Errorf("got an error while writing the local resolver response, error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,615 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import nbdns "github.com/netbirdio/netbird/dns"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/mitchellh/hashstructure/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPort = 53
|
||||||
|
customPort = 5053
|
||||||
|
defaultIP = "127.0.0.1"
|
||||||
|
customIP = "127.0.0.153"
|
||||||
|
)
|
||||||
|
|
||||||
// Server is a dns server interface
|
// Server is a dns server interface
|
||||||
type Server interface {
|
type Server interface {
|
||||||
Start()
|
Initialize() error
|
||||||
Stop()
|
Stop()
|
||||||
|
DnsIP() string
|
||||||
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type registeredHandlerMap map[string]handlerWithStop
|
||||||
|
|
||||||
|
// DefaultServer dns server object
|
||||||
|
type DefaultServer struct {
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
mux sync.Mutex
|
||||||
|
udpFilterHookID string
|
||||||
|
server *dns.Server
|
||||||
|
dnsMux *dns.ServeMux
|
||||||
|
dnsMuxMap registeredHandlerMap
|
||||||
|
localResolver *localResolver
|
||||||
|
wgInterface *iface.WGIface
|
||||||
|
hostManager hostManager
|
||||||
|
updateSerial uint64
|
||||||
|
listenerIsRunning bool
|
||||||
|
runtimePort int
|
||||||
|
runtimeIP string
|
||||||
|
previousConfigHash uint64
|
||||||
|
currentConfig hostDNSConfig
|
||||||
|
customAddress *netip.AddrPort
|
||||||
|
enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type handlerWithStop interface {
|
||||||
|
dns.Handler
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type muxUpdate struct {
|
||||||
|
domain string
|
||||||
|
handler handlerWithStop
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultServer returns a new dns server
|
||||||
|
func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface, customAddress string, initialDnsCfg *nbdns.Config) (*DefaultServer, error) {
|
||||||
|
mux := dns.NewServeMux()
|
||||||
|
|
||||||
|
var addrPort *netip.AddrPort
|
||||||
|
if customAddress != "" {
|
||||||
|
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse the custom dns address, got error: %s", err)
|
||||||
|
}
|
||||||
|
addrPort = &parsedAddrPort
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
defaultServer := &DefaultServer{
|
||||||
|
ctx: ctx,
|
||||||
|
ctxCancel: stop,
|
||||||
|
server: &dns.Server{
|
||||||
|
Net: "udp",
|
||||||
|
Handler: mux,
|
||||||
|
UDPSize: 65535,
|
||||||
|
},
|
||||||
|
dnsMux: mux,
|
||||||
|
dnsMuxMap: make(registeredHandlerMap),
|
||||||
|
localResolver: &localResolver{
|
||||||
|
registeredMap: make(registrationMap),
|
||||||
|
},
|
||||||
|
wgInterface: wgInterface,
|
||||||
|
customAddress: addrPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
if initialDnsCfg != nil {
|
||||||
|
defaultServer.enabled = hasValidDnsServer(initialDnsCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wgInterface.IsUserspaceBind() {
|
||||||
|
defaultServer.evelRuntimeAddressForUserspace()
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultServer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize instantiate host manager. It required to be initialized wginterface
|
||||||
|
func (s *DefaultServer) Initialize() (err error) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
if s.hostManager != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.wgInterface.IsUserspaceBind() {
|
||||||
|
s.evalRuntimeAddress()
|
||||||
|
}
|
||||||
|
s.hostManager, err = newHostManager(s.wgInterface)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen runs the listener in a go routine
|
||||||
|
func (s *DefaultServer) listen() {
|
||||||
|
// nil check required in unit tests
|
||||||
|
if s.wgInterface != nil && s.wgInterface.IsUserspaceBind() {
|
||||||
|
s.udpFilterHookID = s.filterDNSTraffic()
|
||||||
|
s.setListenerStatus(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("starting dns on %s", s.server.Addr)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.setListenerStatus(true)
|
||||||
|
defer s.setListenerStatus(false)
|
||||||
|
|
||||||
|
err := s.server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.runtimePort, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DnsIP returns the DNS resolver server IP address
|
||||||
|
//
|
||||||
|
// When kernel space interface used it return real DNS server listener IP address
|
||||||
|
// For bind interface, fake DNS resolver address returned (second last IP address from Nebird network)
|
||||||
|
func (s *DefaultServer) DnsIP() string {
|
||||||
|
if !s.enabled {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.runtimeIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) getFirstListenerAvailable() (string, int, error) {
|
||||||
|
ips := []string{defaultIP, customIP}
|
||||||
|
if runtime.GOOS != "darwin" && s.wgInterface != nil {
|
||||||
|
ips = append([]string{s.wgInterface.Address().IP.String()}, ips...)
|
||||||
|
}
|
||||||
|
ports := []int{defaultPort, customPort}
|
||||||
|
for _, port := range ports {
|
||||||
|
for _, ip := range ips {
|
||||||
|
addrString := fmt.Sprintf("%s:%d", ip, port)
|
||||||
|
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
||||||
|
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
if err == nil {
|
||||||
|
err = probeListener.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||||
|
}
|
||||||
|
return ip, port, nil
|
||||||
|
}
|
||||||
|
log.Warnf("binding dns on %s is not available, error: %s", addrString, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) setListenerStatus(running bool) {
|
||||||
|
s.listenerIsRunning = running
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the server
|
||||||
|
func (s *DefaultServer) Stop() {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
s.ctxCancel()
|
||||||
|
|
||||||
|
if s.hostManager != nil {
|
||||||
|
err := s.hostManager.restoreHostDNS()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.stopListener()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) stopListener() error {
|
||||||
|
if s.wgInterface != nil && s.wgInterface.IsUserspaceBind() && s.listenerIsRunning {
|
||||||
|
// udpFilterHookID here empty only in the unit tests
|
||||||
|
if filter := s.wgInterface.GetFilter(); filter != nil && s.udpFilterHookID != "" {
|
||||||
|
if err := filter.RemovePacketHook(s.udpFilterHookID); err != nil {
|
||||||
|
log.Errorf("unable to remove DNS packet hook: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.udpFilterHookID = ""
|
||||||
|
s.listenerIsRunning = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.listenerIsRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := s.server.ShutdownContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stopping dns server listener returned an error: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDNSServer processes an update received from the management service
|
||||||
|
func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
log.Infof("not updating DNS server as context is closed")
|
||||||
|
return s.ctx.Err()
|
||||||
|
default:
|
||||||
|
if serial < s.updateSerial {
|
||||||
|
return fmt.Errorf("not applying dns update, error: "+
|
||||||
|
"network update is %d behind the last applied update", s.updateSerial-serial)
|
||||||
|
}
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
if s.hostManager == nil {
|
||||||
|
return fmt.Errorf("dns service is not initialized yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := hashstructure.Hash(update, hashstructure.FormatV2, &hashstructure.HashOptions{
|
||||||
|
ZeroNil: true,
|
||||||
|
IgnoreZeroValue: true,
|
||||||
|
SlicesAsSets: true,
|
||||||
|
UseStringer: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to hash the dns configuration update, got error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.previousConfigHash == hash {
|
||||||
|
log.Debugf("not applying the dns configuration update as there is nothing new")
|
||||||
|
s.updateSerial = serial
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.applyConfiguration(update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.updateSerial = serial
|
||||||
|
s.previousConfigHash = hash
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
|
||||||
|
// is the service should be disabled, we stop the listener or fake resolver
|
||||||
|
// and proceed with a regular update to clean up the handlers and records
|
||||||
|
if !update.ServiceEnable {
|
||||||
|
if err := s.stopListener(); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
} else if !s.listenerIsRunning {
|
||||||
|
s.listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
localMuxUpdates, localRecords, err := s.buildLocalHandlerUpdate(update.CustomZones)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not applying dns update, error: %v", err)
|
||||||
|
}
|
||||||
|
upstreamMuxUpdates, err := s.buildUpstreamHandlerUpdate(update.NameServerGroups)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not applying dns update, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...)
|
||||||
|
|
||||||
|
s.updateMux(muxUpdates)
|
||||||
|
s.updateLocalResolver(localRecords)
|
||||||
|
s.currentConfig = dnsConfigToHostDNSConfig(update, s.runtimeIP, s.runtimePort)
|
||||||
|
|
||||||
|
hostUpdate := s.currentConfig
|
||||||
|
if s.runtimePort != defaultPort && !s.hostManager.supportCustomPort() {
|
||||||
|
log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " +
|
||||||
|
"Learn more at: https://netbird.io/docs/how-to-guides/nameservers#local-resolver")
|
||||||
|
hostUpdate.routeAll = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.hostManager.applyDNSConfig(hostUpdate); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]muxUpdate, map[string]nbdns.SimpleRecord, error) {
|
||||||
|
var muxUpdates []muxUpdate
|
||||||
|
localRecords := make(map[string]nbdns.SimpleRecord, 0)
|
||||||
|
|
||||||
|
for _, customZone := range customZones {
|
||||||
|
|
||||||
|
if len(customZone.Records) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("received an empty list of records")
|
||||||
|
}
|
||||||
|
|
||||||
|
muxUpdates = append(muxUpdates, muxUpdate{
|
||||||
|
domain: customZone.Domain,
|
||||||
|
handler: s.localResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, record := range customZone.Records {
|
||||||
|
var class uint16 = dns.ClassINET
|
||||||
|
if record.Class != nbdns.DefaultClass {
|
||||||
|
return nil, nil, fmt.Errorf("received an invalid class type: %s", record.Class)
|
||||||
|
}
|
||||||
|
key := buildRecordKey(record.Name, class, uint16(record.Type))
|
||||||
|
localRecords[key] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return muxUpdates, localRecords, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.NameServerGroup) ([]muxUpdate, error) {
|
||||||
|
|
||||||
|
var muxUpdates []muxUpdate
|
||||||
|
for _, nsGroup := range nameServerGroups {
|
||||||
|
if len(nsGroup.NameServers) == 0 {
|
||||||
|
log.Warn("received a nameserver group with empty nameserver list")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := newUpstreamResolver(s.ctx)
|
||||||
|
for _, ns := range nsGroup.NameServers {
|
||||||
|
if ns.NSType != nbdns.UDPNameServerType {
|
||||||
|
log.Warnf("skiping nameserver %s with type %s, this peer supports only %s",
|
||||||
|
ns.IP.String(), ns.NSType.String(), nbdns.UDPNameServerType.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handler.upstreamServers = append(handler.upstreamServers, getNSHostPort(ns))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(handler.upstreamServers) == 0 {
|
||||||
|
handler.stop()
|
||||||
|
log.Errorf("received a nameserver group with an invalid nameserver list")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// when upstream fails to resolve domain several times over all it servers
|
||||||
|
// it will calls this hook to exclude self from the configuration and
|
||||||
|
// reapply DNS settings, but it not touch the original configuration and serial number
|
||||||
|
// because it is temporal deactivation until next try
|
||||||
|
//
|
||||||
|
// after some period defined by upstream it trys to reactivate self by calling this hook
|
||||||
|
// everything we need here is just to re-apply current configuration because it already
|
||||||
|
// contains this upstream settings (temporal deactivation not removed it)
|
||||||
|
handler.deactivate, handler.reactivate = s.upstreamCallbacks(nsGroup, handler)
|
||||||
|
|
||||||
|
if nsGroup.Primary {
|
||||||
|
muxUpdates = append(muxUpdates, muxUpdate{
|
||||||
|
domain: nbdns.RootZone,
|
||||||
|
handler: handler,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nsGroup.Domains) == 0 {
|
||||||
|
handler.stop()
|
||||||
|
return nil, fmt.Errorf("received a non primary nameserver group with an empty domain list")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range nsGroup.Domains {
|
||||||
|
if domain == "" {
|
||||||
|
handler.stop()
|
||||||
|
return nil, fmt.Errorf("received a nameserver group with an empty domain element")
|
||||||
|
}
|
||||||
|
muxUpdates = append(muxUpdates, muxUpdate{
|
||||||
|
domain: domain,
|
||||||
|
handler: handler,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return muxUpdates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) updateMux(muxUpdates []muxUpdate) {
|
||||||
|
muxUpdateMap := make(registeredHandlerMap)
|
||||||
|
|
||||||
|
for _, update := range muxUpdates {
|
||||||
|
s.registerMux(update.domain, update.handler)
|
||||||
|
muxUpdateMap[update.domain] = update.handler
|
||||||
|
if existingHandler, ok := s.dnsMuxMap[update.domain]; ok {
|
||||||
|
existingHandler.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, existingHandler := range s.dnsMuxMap {
|
||||||
|
_, found := muxUpdateMap[key]
|
||||||
|
if !found {
|
||||||
|
existingHandler.stop()
|
||||||
|
s.deregisterMux(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dnsMuxMap = muxUpdateMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord) {
|
||||||
|
for key := range s.localResolver.registeredMap {
|
||||||
|
_, found := update[key]
|
||||||
|
if !found {
|
||||||
|
s.localResolver.deleteRecord(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedMap := make(registrationMap)
|
||||||
|
for key, record := range update {
|
||||||
|
err := s.localResolver.registerRecord(record)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("got an error while registering the record (%s), error: %v", record.String(), err)
|
||||||
|
}
|
||||||
|
updatedMap[key] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.localResolver.registeredMap = updatedMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNSHostPort(ns nbdns.NameServer) string {
|
||||||
|
return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) registerMux(pattern string, handler dns.Handler) {
|
||||||
|
s.dnsMux.Handle(pattern, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) deregisterMux(pattern string) {
|
||||||
|
s.dnsMux.HandleRemove(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// upstreamCallbacks returns two functions, the first one is used to deactivate
|
||||||
|
// the upstream resolver from the configuration, the second one is used to
|
||||||
|
// reactivate it. Not allowed to call reactivate before deactivate.
|
||||||
|
func (s *DefaultServer) upstreamCallbacks(
|
||||||
|
nsGroup *nbdns.NameServerGroup,
|
||||||
|
handler dns.Handler,
|
||||||
|
) (deactivate func(), reactivate func()) {
|
||||||
|
var removeIndex map[string]int
|
||||||
|
deactivate = func() {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
l := log.WithField("nameservers", nsGroup.NameServers)
|
||||||
|
l.Info("temporary deactivate nameservers group due timeout")
|
||||||
|
|
||||||
|
removeIndex = make(map[string]int)
|
||||||
|
for _, domain := range nsGroup.Domains {
|
||||||
|
removeIndex[domain] = -1
|
||||||
|
}
|
||||||
|
if nsGroup.Primary {
|
||||||
|
removeIndex[nbdns.RootZone] = -1
|
||||||
|
s.currentConfig.routeAll = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range s.currentConfig.domains {
|
||||||
|
if _, found := removeIndex[item.domain]; found {
|
||||||
|
s.currentConfig.domains[i].disabled = true
|
||||||
|
s.deregisterMux(item.domain)
|
||||||
|
removeIndex[item.domain] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
||||||
|
l.WithError(err).Error("fail to apply nameserver deactivation on the host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reactivate = func() {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
for domain, i := range removeIndex {
|
||||||
|
if i == -1 || i >= len(s.currentConfig.domains) || s.currentConfig.domains[i].domain != domain {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.currentConfig.domains[i].disabled = false
|
||||||
|
s.registerMux(domain, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := log.WithField("nameservers", nsGroup.NameServers)
|
||||||
|
l.Debug("reactivate temporary disabled nameserver group")
|
||||||
|
|
||||||
|
if nsGroup.Primary {
|
||||||
|
s.currentConfig.routeAll = true
|
||||||
|
}
|
||||||
|
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
||||||
|
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) filterDNSTraffic() string {
|
||||||
|
filter := s.wgInterface.GetFilter()
|
||||||
|
if filter == nil {
|
||||||
|
log.Error("can't set DNS filter, filter not initialized")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
firstLayerDecoder := layers.LayerTypeIPv4
|
||||||
|
if s.wgInterface.Address().Network.IP.To4() == nil {
|
||||||
|
firstLayerDecoder = layers.LayerTypeIPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := func(packetData []byte) bool {
|
||||||
|
// Decode the packet
|
||||||
|
packet := gopacket.NewPacket(packetData, firstLayerDecoder, gopacket.Default)
|
||||||
|
|
||||||
|
// Get the UDP layer
|
||||||
|
udpLayer := packet.Layer(layers.LayerTypeUDP)
|
||||||
|
udp := udpLayer.(*layers.UDP)
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
if err := msg.Unpack(udp.Payload); err != nil {
|
||||||
|
log.Tracef("parse DNS request: %v", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := responseWriter{
|
||||||
|
packet: packet,
|
||||||
|
device: s.wgInterface.GetDevice().Device,
|
||||||
|
}
|
||||||
|
go s.dnsMux.ServeDNS(&writer, msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter.AddUDPPacketHook(false, net.ParseIP(s.runtimeIP), uint16(s.runtimePort), hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) evelRuntimeAddressForUserspace() {
|
||||||
|
s.runtimeIP = getLastIPFromNetwork(s.wgInterface.Address().Network, 1)
|
||||||
|
s.runtimePort = defaultPort
|
||||||
|
s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, s.runtimePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) evalRuntimeAddress() {
|
||||||
|
defer func() {
|
||||||
|
s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, s.runtimePort)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if s.customAddress != nil {
|
||||||
|
s.runtimeIP = s.customAddress.Addr().String()
|
||||||
|
s.runtimePort = int(s.customAddress.Port())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, port, err := s.getFirstListenerAvailable()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.runtimeIP = ip
|
||||||
|
s.runtimePort = port
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLastIPFromNetwork(network *net.IPNet, fromEnd int) string {
|
||||||
|
// Calculate the last IP in the CIDR range
|
||||||
|
var endIP net.IP
|
||||||
|
for i := 0; i < len(network.IP); i++ {
|
||||||
|
endIP = append(endIP, network.IP[i]|^network.Mask[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to big.Int
|
||||||
|
endInt := big.NewInt(0)
|
||||||
|
endInt.SetBytes(endIP)
|
||||||
|
|
||||||
|
// subtract fromEnd from the last ip
|
||||||
|
fromEndBig := big.NewInt(int64(fromEnd))
|
||||||
|
resultInt := big.NewInt(0)
|
||||||
|
resultInt.Sub(endInt, fromEndBig)
|
||||||
|
|
||||||
|
return net.IP(resultInt.Bytes()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasValidDnsServer(cfg *nbdns.Config) bool {
|
||||||
|
for _, c := range cfg.NameServerGroups {
|
||||||
|
if c.Primary {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultServer dummy dns server
|
|
||||||
type DefaultServer struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultServer On Android the DNS feature is not supported yet
|
|
||||||
func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface, customAddress string) (*DefaultServer, error) {
|
|
||||||
return &DefaultServer{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start dummy implementation
|
|
||||||
func (s DefaultServer) Start() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop dummy implementation
|
|
||||||
func (s DefaultServer) Stop() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDNSServer dummy implementation
|
|
||||||
func (s DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
//go:build !android
|
|
||||||
|
|
||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/mitchellh/hashstructure/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultPort = 53
|
|
||||||
customPort = 5053
|
|
||||||
defaultIP = "127.0.0.1"
|
|
||||||
customIP = "127.0.0.153"
|
|
||||||
)
|
|
||||||
|
|
||||||
type registeredHandlerMap map[string]handlerWithStop
|
|
||||||
|
|
||||||
// DefaultServer dns server object
|
|
||||||
type DefaultServer struct {
|
|
||||||
ctx context.Context
|
|
||||||
ctxCancel context.CancelFunc
|
|
||||||
mux sync.Mutex
|
|
||||||
server *dns.Server
|
|
||||||
dnsMux *dns.ServeMux
|
|
||||||
dnsMuxMap registeredHandlerMap
|
|
||||||
localResolver *localResolver
|
|
||||||
wgInterface *iface.WGIface
|
|
||||||
hostManager hostManager
|
|
||||||
updateSerial uint64
|
|
||||||
listenerIsRunning bool
|
|
||||||
runtimePort int
|
|
||||||
runtimeIP string
|
|
||||||
previousConfigHash uint64
|
|
||||||
currentConfig hostDNSConfig
|
|
||||||
customAddress *netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
type handlerWithStop interface {
|
|
||||||
dns.Handler
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
type muxUpdate struct {
|
|
||||||
domain string
|
|
||||||
handler handlerWithStop
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultServer returns a new dns server
|
|
||||||
func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface, customAddress string) (*DefaultServer, error) {
|
|
||||||
mux := dns.NewServeMux()
|
|
||||||
|
|
||||||
dnsServer := &dns.Server{
|
|
||||||
Net: "udp",
|
|
||||||
Handler: mux,
|
|
||||||
UDPSize: 65535,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, stop := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
var addrPort *netip.AddrPort
|
|
||||||
if customAddress != "" {
|
|
||||||
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
|
|
||||||
if err != nil {
|
|
||||||
stop()
|
|
||||||
return nil, fmt.Errorf("unable to parse the custom dns address, got error: %s", err)
|
|
||||||
}
|
|
||||||
addrPort = &parsedAddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultServer := &DefaultServer{
|
|
||||||
ctx: ctx,
|
|
||||||
ctxCancel: stop,
|
|
||||||
server: dnsServer,
|
|
||||||
dnsMux: mux,
|
|
||||||
dnsMuxMap: make(registeredHandlerMap),
|
|
||||||
localResolver: &localResolver{
|
|
||||||
registeredMap: make(registrationMap),
|
|
||||||
},
|
|
||||||
wgInterface: wgInterface,
|
|
||||||
runtimePort: defaultPort,
|
|
||||||
customAddress: addrPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
hostmanager, err := newHostManager(wgInterface)
|
|
||||||
if err != nil {
|
|
||||||
stop()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defaultServer.hostManager = hostmanager
|
|
||||||
return defaultServer, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start runs the listener in a go routine
|
|
||||||
func (s *DefaultServer) Start() {
|
|
||||||
if s.customAddress != nil {
|
|
||||||
s.runtimeIP = s.customAddress.Addr().String()
|
|
||||||
s.runtimePort = int(s.customAddress.Port())
|
|
||||||
} else {
|
|
||||||
ip, port, err := s.getFirstListenerAvailable()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.runtimeIP = ip
|
|
||||||
s.runtimePort = port
|
|
||||||
}
|
|
||||||
|
|
||||||
s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, s.runtimePort)
|
|
||||||
|
|
||||||
log.Debugf("starting dns on %s", s.server.Addr)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
s.setListenerStatus(true)
|
|
||||||
defer s.setListenerStatus(false)
|
|
||||||
|
|
||||||
err := s.server.ListenAndServe()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.runtimePort, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) getFirstListenerAvailable() (string, int, error) {
|
|
||||||
ips := []string{defaultIP, customIP}
|
|
||||||
if runtime.GOOS != "darwin" && s.wgInterface != nil {
|
|
||||||
ips = append([]string{s.wgInterface.Address().IP.String()}, ips...)
|
|
||||||
}
|
|
||||||
ports := []int{defaultPort, customPort}
|
|
||||||
for _, port := range ports {
|
|
||||||
for _, ip := range ips {
|
|
||||||
addrString := fmt.Sprintf("%s:%d", ip, port)
|
|
||||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
|
||||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
|
||||||
if err == nil {
|
|
||||||
err = probeListener.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("got an error closing the probe listener, error: %s", err)
|
|
||||||
}
|
|
||||||
return ip, port, nil
|
|
||||||
}
|
|
||||||
log.Warnf("binding dns on %s is not available, error: %s", addrString, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) setListenerStatus(running bool) {
|
|
||||||
s.listenerIsRunning = running
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the server
|
|
||||||
func (s *DefaultServer) Stop() {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
s.ctxCancel()
|
|
||||||
|
|
||||||
err := s.hostManager.restoreHostDNS()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.stopListener()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) stopListener() error {
|
|
||||||
if !s.listenerIsRunning {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := s.server.ShutdownContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("stopping dns server listener returned an error: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDNSServer processes an update received from the management service
|
|
||||||
func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
|
||||||
select {
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
log.Infof("not updating DNS server as context is closed")
|
|
||||||
return s.ctx.Err()
|
|
||||||
default:
|
|
||||||
if serial < s.updateSerial {
|
|
||||||
return fmt.Errorf("not applying dns update, error: "+
|
|
||||||
"network update is %d behind the last applied update", s.updateSerial-serial)
|
|
||||||
}
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
hash, err := hashstructure.Hash(update, hashstructure.FormatV2, &hashstructure.HashOptions{
|
|
||||||
ZeroNil: true,
|
|
||||||
IgnoreZeroValue: true,
|
|
||||||
SlicesAsSets: true,
|
|
||||||
UseStringer: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("unable to hash the dns configuration update, got error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.previousConfigHash == hash {
|
|
||||||
log.Debugf("not applying the dns configuration update as there is nothing new")
|
|
||||||
s.updateSerial = serial
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.applyConfiguration(update); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.updateSerial = serial
|
|
||||||
s.previousConfigHash = hash
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
|
|
||||||
// is the service should be disabled, we stop the listener
|
|
||||||
// and proceed with a regular update to clean up the handlers and records
|
|
||||||
if !update.ServiceEnable {
|
|
||||||
err := s.stopListener()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
} else if !s.listenerIsRunning {
|
|
||||||
s.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
localMuxUpdates, localRecords, err := s.buildLocalHandlerUpdate(update.CustomZones)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("not applying dns update, error: %v", err)
|
|
||||||
}
|
|
||||||
upstreamMuxUpdates, err := s.buildUpstreamHandlerUpdate(update.NameServerGroups)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("not applying dns update, error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...)
|
|
||||||
|
|
||||||
s.updateMux(muxUpdates)
|
|
||||||
s.updateLocalResolver(localRecords)
|
|
||||||
s.currentConfig = dnsConfigToHostDNSConfig(update, s.runtimeIP, s.runtimePort)
|
|
||||||
|
|
||||||
hostUpdate := s.currentConfig
|
|
||||||
if s.runtimePort != defaultPort && !s.hostManager.supportCustomPort() {
|
|
||||||
log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " +
|
|
||||||
"Learn more at: https://netbird.io/docs/how-to-guides/nameservers#local-resolver")
|
|
||||||
hostUpdate.routeAll = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.hostManager.applyDNSConfig(hostUpdate); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]muxUpdate, map[string]nbdns.SimpleRecord, error) {
|
|
||||||
var muxUpdates []muxUpdate
|
|
||||||
localRecords := make(map[string]nbdns.SimpleRecord, 0)
|
|
||||||
|
|
||||||
for _, customZone := range customZones {
|
|
||||||
|
|
||||||
if len(customZone.Records) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("received an empty list of records")
|
|
||||||
}
|
|
||||||
|
|
||||||
muxUpdates = append(muxUpdates, muxUpdate{
|
|
||||||
domain: customZone.Domain,
|
|
||||||
handler: s.localResolver,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, record := range customZone.Records {
|
|
||||||
var class uint16 = dns.ClassINET
|
|
||||||
if record.Class != nbdns.DefaultClass {
|
|
||||||
return nil, nil, fmt.Errorf("received an invalid class type: %s", record.Class)
|
|
||||||
}
|
|
||||||
key := buildRecordKey(record.Name, class, uint16(record.Type))
|
|
||||||
localRecords[key] = record
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return muxUpdates, localRecords, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.NameServerGroup) ([]muxUpdate, error) {
|
|
||||||
|
|
||||||
var muxUpdates []muxUpdate
|
|
||||||
for _, nsGroup := range nameServerGroups {
|
|
||||||
if len(nsGroup.NameServers) == 0 {
|
|
||||||
log.Warn("received a nameserver group with empty nameserver list")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := newUpstreamResolver(s.ctx)
|
|
||||||
for _, ns := range nsGroup.NameServers {
|
|
||||||
if ns.NSType != nbdns.UDPNameServerType {
|
|
||||||
log.Warnf("skiping nameserver %s with type %s, this peer supports only %s",
|
|
||||||
ns.IP.String(), ns.NSType.String(), nbdns.UDPNameServerType.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
handler.upstreamServers = append(handler.upstreamServers, getNSHostPort(ns))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(handler.upstreamServers) == 0 {
|
|
||||||
handler.stop()
|
|
||||||
log.Errorf("received a nameserver group with an invalid nameserver list")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// when upstream fails to resolve domain several times over all it servers
|
|
||||||
// it will calls this hook to exclude self from the configuration and
|
|
||||||
// reapply DNS settings, but it not touch the original configuration and serial number
|
|
||||||
// because it is temporal deactivation until next try
|
|
||||||
//
|
|
||||||
// after some period defined by upstream it trys to reactivate self by calling this hook
|
|
||||||
// everything we need here is just to re-apply current configuration because it already
|
|
||||||
// contains this upstream settings (temporal deactivation not removed it)
|
|
||||||
handler.deactivate, handler.reactivate = s.upstreamCallbacks(nsGroup, handler)
|
|
||||||
|
|
||||||
if nsGroup.Primary {
|
|
||||||
muxUpdates = append(muxUpdates, muxUpdate{
|
|
||||||
domain: nbdns.RootZone,
|
|
||||||
handler: handler,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nsGroup.Domains) == 0 {
|
|
||||||
handler.stop()
|
|
||||||
return nil, fmt.Errorf("received a non primary nameserver group with an empty domain list")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, domain := range nsGroup.Domains {
|
|
||||||
if domain == "" {
|
|
||||||
handler.stop()
|
|
||||||
return nil, fmt.Errorf("received a nameserver group with an empty domain element")
|
|
||||||
}
|
|
||||||
muxUpdates = append(muxUpdates, muxUpdate{
|
|
||||||
domain: domain,
|
|
||||||
handler: handler,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return muxUpdates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) updateMux(muxUpdates []muxUpdate) {
|
|
||||||
muxUpdateMap := make(registeredHandlerMap)
|
|
||||||
|
|
||||||
for _, update := range muxUpdates {
|
|
||||||
s.registerMux(update.domain, update.handler)
|
|
||||||
muxUpdateMap[update.domain] = update.handler
|
|
||||||
if existingHandler, ok := s.dnsMuxMap[update.domain]; ok {
|
|
||||||
existingHandler.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, existingHandler := range s.dnsMuxMap {
|
|
||||||
_, found := muxUpdateMap[key]
|
|
||||||
if !found {
|
|
||||||
existingHandler.stop()
|
|
||||||
s.deregisterMux(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.dnsMuxMap = muxUpdateMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord) {
|
|
||||||
for key := range s.localResolver.registeredMap {
|
|
||||||
_, found := update[key]
|
|
||||||
if !found {
|
|
||||||
s.localResolver.deleteRecord(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedMap := make(registrationMap)
|
|
||||||
for key, record := range update {
|
|
||||||
err := s.localResolver.registerRecord(record)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("got an error while registering the record (%s), error: %v", record.String(), err)
|
|
||||||
}
|
|
||||||
updatedMap[key] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.localResolver.registeredMap = updatedMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNSHostPort(ns nbdns.NameServer) string {
|
|
||||||
return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) registerMux(pattern string, handler dns.Handler) {
|
|
||||||
s.dnsMux.Handle(pattern, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultServer) deregisterMux(pattern string) {
|
|
||||||
s.dnsMux.HandleRemove(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// upstreamCallbacks returns two functions, the first one is used to deactivate
|
|
||||||
// the upstream resolver from the configuration, the second one is used to
|
|
||||||
// reactivate it. Not allowed to call reactivate before deactivate.
|
|
||||||
func (s *DefaultServer) upstreamCallbacks(
|
|
||||||
nsGroup *nbdns.NameServerGroup,
|
|
||||||
handler dns.Handler,
|
|
||||||
) (deactivate func(), reactivate func()) {
|
|
||||||
var removeIndex map[string]int
|
|
||||||
deactivate = func() {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
l := log.WithField("nameservers", nsGroup.NameServers)
|
|
||||||
l.Info("temporary deactivate nameservers group due timeout")
|
|
||||||
|
|
||||||
removeIndex = make(map[string]int)
|
|
||||||
for _, domain := range nsGroup.Domains {
|
|
||||||
removeIndex[domain] = -1
|
|
||||||
}
|
|
||||||
if nsGroup.Primary {
|
|
||||||
removeIndex[nbdns.RootZone] = -1
|
|
||||||
s.currentConfig.routeAll = false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, item := range s.currentConfig.domains {
|
|
||||||
if _, found := removeIndex[item.domain]; found {
|
|
||||||
s.currentConfig.domains[i].disabled = true
|
|
||||||
s.deregisterMux(item.domain)
|
|
||||||
removeIndex[item.domain] = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
|
||||||
l.WithError(err).Error("fail to apply nameserver deactivation on the host")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reactivate = func() {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
for domain, i := range removeIndex {
|
|
||||||
if i == -1 || i >= len(s.currentConfig.domains) || s.currentConfig.domains[i].domain != domain {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.currentConfig.domains[i].disabled = false
|
|
||||||
s.registerMux(domain, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := log.WithField("nameservers", nsGroup.NameServers)
|
|
||||||
l.Debug("reactivate temporary disabled nameserver group")
|
|
||||||
|
|
||||||
if nsGroup.Primary {
|
|
||||||
s.currentConfig.routeAll = true
|
|
||||||
}
|
|
||||||
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
|
||||||
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -5,16 +5,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
pfmock "github.com/netbirdio/netbird/iface/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
var zoneRecords = []nbdns.SimpleRecord{
|
var zoneRecords = []nbdns.SimpleRecord{
|
||||||
@@ -222,7 +224,11 @@ func TestUpdateDNSServer(t *testing.T) {
|
|||||||
t.Log(err)
|
t.Log(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "")
|
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = dnsServer.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -272,6 +278,133 @@ func TestUpdateDNSServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDNSFakeResolverHandleUpdates(t *testing.T) {
|
||||||
|
ov := os.Getenv("NB_WG_KERNEL_DISABLED")
|
||||||
|
defer os.Setenv("NB_WG_KERNEL_DISABLED", ov)
|
||||||
|
|
||||||
|
os.Setenv("NB_WG_KERNEL_DISABLED", "true")
|
||||||
|
newNet, err := stdnet.NewNet(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("create stdnet: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", iface.DefaultMTU, nil, newNet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("build interface wireguard: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wgIface.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("crate and init wireguard interface: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = wgIface.Close(); err != nil {
|
||||||
|
t.Logf("close wireguard interface: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
_, ipNet, err := net.ParseCIDR("100.66.100.1/32")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("parse CIDR: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packetfilter := pfmock.NewMockPacketFilter(ctrl)
|
||||||
|
packetfilter.EXPECT().SetNetwork(ipNet)
|
||||||
|
packetfilter.EXPECT().DropOutgoing(gomock.Any()).AnyTimes()
|
||||||
|
packetfilter.EXPECT().AddUDPPacketHook(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||||
|
packetfilter.EXPECT().RemovePacketHook(gomock.Any()).AnyTimes()
|
||||||
|
|
||||||
|
if err := wgIface.SetFilter(packetfilter); err != nil {
|
||||||
|
t.Errorf("set packet filter: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("create DNS server: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dnsServer.Initialize()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("run DNS server: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = dnsServer.hostManager.restoreHostDNS(); err != nil {
|
||||||
|
t.Logf("restore DNS settings on the host: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
dnsServer.dnsMuxMap = registeredHandlerMap{zoneRecords[0].Name: &localResolver{}}
|
||||||
|
dnsServer.localResolver.registeredMap = registrationMap{"netbird.cloud": struct{}{}}
|
||||||
|
dnsServer.updateSerial = 0
|
||||||
|
|
||||||
|
nameServers := []nbdns.NameServer{
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("8.8.8.8"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("8.8.4.4"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
update := nbdns.Config{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud",
|
||||||
|
Records: zoneRecords,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
Domains: []string{"netbird.io"},
|
||||||
|
NameServers: nameServers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NameServers: nameServers,
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the server with regular configuration
|
||||||
|
if err := dnsServer.UpdateDNSServer(1, update); err != nil {
|
||||||
|
t.Fatalf("update dns server should not fail, got error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
update2 := update
|
||||||
|
update2.ServiceEnable = false
|
||||||
|
// Disable the server, stop the listener
|
||||||
|
if err := dnsServer.UpdateDNSServer(2, update2); err != nil {
|
||||||
|
t.Fatalf("update dns server should not fail, got error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
update3 := update2
|
||||||
|
update3.NameServerGroups = update3.NameServerGroups[:1]
|
||||||
|
// But service still get updates and we checking that we handle
|
||||||
|
// internal state in the right way
|
||||||
|
if err := dnsServer.UpdateDNSServer(3, update3); err != nil {
|
||||||
|
t.Fatalf("update dns server should not fail, got error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDNSServerStartStop(t *testing.T) {
|
func TestDNSServerStartStop(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -291,7 +424,7 @@ func TestDNSServerStartStop(t *testing.T) {
|
|||||||
dnsServer := getDefaultServerWithNoHostManager(t, testCase.addrPort)
|
dnsServer := getDefaultServerWithNoHostManager(t, testCase.addrPort)
|
||||||
|
|
||||||
dnsServer.hostManager = newNoopHostMocker()
|
dnsServer.hostManager = newNoopHostMocker()
|
||||||
dnsServer.Start()
|
dnsServer.listen()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
if !dnsServer.listenerIsRunning {
|
if !dnsServer.listenerIsRunning {
|
||||||
t.Fatal("dns server listener is not running")
|
t.Fatal("dns server listener is not running")
|
||||||
@@ -428,7 +561,7 @@ func getDefaultServerWithNoHostManager(t *testing.T, addrPort string) *DefaultSe
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
return &DefaultServer{
|
ds := &DefaultServer{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
ctxCancel: cancel,
|
ctxCancel: cancel,
|
||||||
server: dnsServer,
|
server: dnsServer,
|
||||||
@@ -439,4 +572,31 @@ func getDefaultServerWithNoHostManager(t *testing.T, addrPort string) *DefaultSe
|
|||||||
},
|
},
|
||||||
customAddress: parsedAddrPort,
|
customAddress: parsedAddrPort,
|
||||||
}
|
}
|
||||||
|
ds.evalRuntimeAddress()
|
||||||
|
return ds
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetLastIPFromNetwork(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
addr string
|
||||||
|
ip string
|
||||||
|
}{
|
||||||
|
{"2001:db8::/32", "2001:db8:ffff:ffff:ffff:ffff:ffff:fffe"},
|
||||||
|
{"192.168.0.0/30", "192.168.0.2"},
|
||||||
|
{"192.168.0.0/16", "192.168.255.254"},
|
||||||
|
{"192.168.0.0/24", "192.168.0.254"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
_, ipnet, err := net.ParseCIDR(tt.addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing CIDR: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIP := getLastIPFromNetwork(ipnet, 1)
|
||||||
|
if lastIP != tt.ip {
|
||||||
|
t.Errorf("wrong IP address, expected %s: got %s", tt.ip, lastIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -189,14 +189,37 @@ func (e *Engine) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := e.readInitialRoutes()
|
var routes []*route.Route
|
||||||
if err != nil {
|
var dnsCfg *nbdns.Config
|
||||||
return err
|
|
||||||
|
if runtime.GOOS == "android" {
|
||||||
|
routes, dnsCfg, err = e.readInitialSettings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.dnsServer == nil {
|
||||||
|
// todo fix custom address
|
||||||
|
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, dnsCfg)
|
||||||
|
if err != nil {
|
||||||
|
e.close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.dnsServer = dnsServer
|
||||||
|
}
|
||||||
|
|
||||||
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes)
|
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes)
|
||||||
e.routeManager.SetRouteChangeListener(e.mobileDep.RouteListener)
|
e.routeManager.SetRouteChangeListener(e.mobileDep.RouteListener)
|
||||||
|
|
||||||
err = e.wgInterface.Create()
|
if runtime.GOOS != "android" {
|
||||||
|
err = e.wgInterface.Create()
|
||||||
|
} else {
|
||||||
|
err = e.wgInterface.CreateOnMobile(iface.MobileIFaceArguments{
|
||||||
|
Routes: e.routeManager.InitialRouteRange(),
|
||||||
|
Dns: e.dnsServer.DnsIP(),
|
||||||
|
})
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error())
|
log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error())
|
||||||
e.close()
|
e.close()
|
||||||
@@ -236,14 +259,10 @@ func (e *Engine) Start() error {
|
|||||||
e.acl = acl
|
e.acl = acl
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.dnsServer == nil {
|
err = e.dnsServer.Initialize()
|
||||||
// todo fix custom address
|
if err != nil {
|
||||||
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress)
|
e.close()
|
||||||
if err != nil {
|
return err
|
||||||
e.close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.dnsServer = dnsServer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.receiveSignalEvents()
|
e.receiveSignalEvents()
|
||||||
@@ -586,6 +605,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
// cleanup request, most likely our peer has been deleted
|
// cleanup request, most likely our peer has been deleted
|
||||||
if networkMap.GetRemotePeersIsEmpty() {
|
if networkMap.GetRemotePeersIsEmpty() {
|
||||||
err := e.removeAllPeers()
|
err := e.removeAllPeers()
|
||||||
|
e.statusRecorder.FinishPeerListModifications()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -605,6 +625,8 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.statusRecorder.FinishPeerListModifications()
|
||||||
|
|
||||||
// update SSHServer by adding remote peer SSH keys
|
// update SSHServer by adding remote peer SSH keys
|
||||||
if !isNil(e.sshServer) {
|
if !isNil(e.sshServer) {
|
||||||
for _, config := range networkMap.GetRemotePeers() {
|
for _, config := range networkMap.GetRemotePeers() {
|
||||||
@@ -740,17 +762,13 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
|
|||||||
}
|
}
|
||||||
e.peerConns[peerKey] = conn
|
e.peerConns[peerKey] = conn
|
||||||
|
|
||||||
err = e.statusRecorder.AddPeer(peerKey)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
go e.connWorker(conn, peerKey)
|
go e.connWorker(conn, peerKey)
|
||||||
}
|
}
|
||||||
err := e.statusRecorder.UpdatePeerFQDN(peerKey, peerConfig.Fqdn)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("error updating peer's %s fqdn in the status recorder, got error: %v", peerKey, err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,14 +811,14 @@ func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Engine) peerExists(peerKey string) bool {
|
func (e *Engine) peerExists(peerKey string) bool {
|
||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
_, ok := e.peerConns[peerKey]
|
_, ok := e.peerConns[peerKey]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
||||||
log.Debugf("creating peer connection %s", pubKey)
|
log.Debugf("creating peer connection %s", pubKey)
|
||||||
var stunTurn []*ice.URL
|
var stunTurn []*ice.URL
|
||||||
stunTurn = append(stunTurn, e.STUNs...)
|
stunTurn = append(stunTurn, e.STUNs...)
|
||||||
@@ -870,7 +888,6 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
err := e.signal.Receive(func(msg *sProto.Message) error {
|
err := e.signal.Receive(func(msg *sProto.Message) error {
|
||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
conn := e.peerConns[msg.Key]
|
conn := e.peerConns[msg.Key]
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||||
@@ -1027,17 +1044,14 @@ func (e *Engine) close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) readInitialRoutes() ([]*route.Route, error) {
|
func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) {
|
||||||
if runtime.GOOS != "android" {
|
netMap, err := e.mgmClient.GetNetworkMap()
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
routesResp, err := e.mgmClient.GetRoutes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return toRoutes(routesResp), nil
|
routes := toRoutes(netMap.GetRoutes())
|
||||||
|
dnsCfg := toDNSConfig(netMap.GetDNSConfig())
|
||||||
|
return routes, &dnsCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ type Status struct {
|
|||||||
mgmAddress string
|
mgmAddress string
|
||||||
signalAddress string
|
signalAddress string
|
||||||
notifier *notifier
|
notifier *notifier
|
||||||
|
|
||||||
|
// To reduce the number of notification invocation this bool will be true when need to call the notification
|
||||||
|
// Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events
|
||||||
|
// set to true this variable and at the end of the processing we will reset it by the FinishPeerListModifications()
|
||||||
|
peerListChangedForNotification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRecorder returns a new Status instance
|
// NewRecorder returns a new Status instance
|
||||||
@@ -78,11 +83,13 @@ func (d *Status) ReplaceOfflinePeers(replacement []State) {
|
|||||||
defer d.mux.Unlock()
|
defer d.mux.Unlock()
|
||||||
d.offlinePeers = make([]State, len(replacement))
|
d.offlinePeers = make([]State, len(replacement))
|
||||||
copy(d.offlinePeers, replacement)
|
copy(d.offlinePeers, replacement)
|
||||||
d.notifyPeerListChanged()
|
|
||||||
|
// todo we should set to true in case if the list changed only
|
||||||
|
d.peerListChangedForNotification = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPeer adds peer to Daemon status map
|
// AddPeer adds peer to Daemon status map
|
||||||
func (d *Status) AddPeer(peerPubKey string) error {
|
func (d *Status) AddPeer(peerPubKey string, fqdn string) error {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
defer d.mux.Unlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
@@ -90,7 +97,12 @@ func (d *Status) AddPeer(peerPubKey string) error {
|
|||||||
if ok {
|
if ok {
|
||||||
return errors.New("peer already exist")
|
return errors.New("peer already exist")
|
||||||
}
|
}
|
||||||
d.peers[peerPubKey] = State{PubKey: peerPubKey, ConnStatus: StatusDisconnected}
|
d.peers[peerPubKey] = State{
|
||||||
|
PubKey: peerPubKey,
|
||||||
|
ConnStatus: StatusDisconnected,
|
||||||
|
FQDN: fqdn,
|
||||||
|
}
|
||||||
|
d.peerListChangedForNotification = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,13 +124,13 @@ func (d *Status) RemovePeer(peerPubKey string) error {
|
|||||||
defer d.mux.Unlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
_, ok := d.peers[peerPubKey]
|
_, ok := d.peers[peerPubKey]
|
||||||
if ok {
|
if !ok {
|
||||||
delete(d.peers, peerPubKey)
|
return errors.New("no peer with to remove")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.notifyPeerListChanged()
|
delete(d.peers, peerPubKey)
|
||||||
return errors.New("no peer with to remove")
|
d.peerListChangedForNotification = true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePeerState updates peer status
|
// UpdatePeerState updates peer status
|
||||||
@@ -135,6 +147,8 @@ func (d *Status) UpdatePeerState(receivedState State) error {
|
|||||||
peerState.IP = receivedState.IP
|
peerState.IP = receivedState.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipNotification := shouldSkipNotify(receivedState, peerState)
|
||||||
|
|
||||||
if receivedState.ConnStatus != peerState.ConnStatus {
|
if receivedState.ConnStatus != peerState.ConnStatus {
|
||||||
peerState.ConnStatus = receivedState.ConnStatus
|
peerState.ConnStatus = receivedState.ConnStatus
|
||||||
peerState.ConnStatusUpdate = receivedState.ConnStatusUpdate
|
peerState.ConnStatusUpdate = receivedState.ConnStatusUpdate
|
||||||
@@ -146,8 +160,7 @@ func (d *Status) UpdatePeerState(receivedState State) error {
|
|||||||
|
|
||||||
d.peers[receivedState.PubKey] = peerState
|
d.peers[receivedState.PubKey] = peerState
|
||||||
|
|
||||||
if receivedState.ConnStatus == StatusConnecting ||
|
if skipNotification {
|
||||||
(receivedState.ConnStatus == StatusDisconnected && peerState.ConnStatus == StatusConnecting) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +174,19 @@ func (d *Status) UpdatePeerState(receivedState State) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldSkipNotify(new, curr State) bool {
|
||||||
|
switch {
|
||||||
|
case new.ConnStatus == StatusConnecting:
|
||||||
|
return true
|
||||||
|
case new.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusConnecting:
|
||||||
|
return true
|
||||||
|
case new.ConnStatus == StatusDisconnected && curr.ConnStatus == StatusDisconnected:
|
||||||
|
return curr.IP != ""
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UpdatePeerFQDN update peer's state fqdn only
|
// UpdatePeerFQDN update peer's state fqdn only
|
||||||
func (d *Status) UpdatePeerFQDN(peerPubKey, fqdn string) error {
|
func (d *Status) UpdatePeerFQDN(peerPubKey, fqdn string) error {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
@@ -174,10 +200,23 @@ func (d *Status) UpdatePeerFQDN(peerPubKey, fqdn string) error {
|
|||||||
peerState.FQDN = fqdn
|
peerState.FQDN = fqdn
|
||||||
d.peers[peerPubKey] = peerState
|
d.peers[peerPubKey] = peerState
|
||||||
|
|
||||||
d.notifyPeerListChanged()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FinishPeerListModifications this event invoke the notification
|
||||||
|
func (d *Status) FinishPeerListModifications() {
|
||||||
|
d.mux.Lock()
|
||||||
|
|
||||||
|
if !d.peerListChangedForNotification {
|
||||||
|
d.mux.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.peerListChangedForNotification = false
|
||||||
|
d.mux.Unlock()
|
||||||
|
|
||||||
|
d.notifyPeerListChanged()
|
||||||
|
}
|
||||||
|
|
||||||
// GetPeerStateChangeNotifier returns a change notifier channel for a peer
|
// GetPeerStateChangeNotifier returns a change notifier channel for a peer
|
||||||
func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
|
func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import (
|
|||||||
func TestAddPeer(t *testing.T) {
|
func TestAddPeer(t *testing.T) {
|
||||||
key := "abc"
|
key := "abc"
|
||||||
status := NewRecorder("https://mgm")
|
status := NewRecorder("https://mgm")
|
||||||
err := status.AddPeer(key)
|
err := status.AddPeer(key, "abc.netbird")
|
||||||
assert.NoError(t, err, "shouldn't return error")
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
_, exists := status.peers[key]
|
_, exists := status.peers[key]
|
||||||
assert.True(t, exists, "value was found")
|
assert.True(t, exists, "value was found")
|
||||||
|
|
||||||
err = status.AddPeer(key)
|
err = status.AddPeer(key, "abc.netbird")
|
||||||
|
|
||||||
assert.Error(t, err, "should return error on duplicate")
|
assert.Error(t, err, "should return error on duplicate")
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ func TestAddPeer(t *testing.T) {
|
|||||||
func TestGetPeer(t *testing.T) {
|
func TestGetPeer(t *testing.T) {
|
||||||
key := "abc"
|
key := "abc"
|
||||||
status := NewRecorder("https://mgm")
|
status := NewRecorder("https://mgm")
|
||||||
err := status.AddPeer(key)
|
err := status.AddPeer(key, "abc.netbird")
|
||||||
assert.NoError(t, err, "shouldn't return error")
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
peerStatus, err := status.GetPeer(key)
|
peerStatus, err := status.GetPeer(key)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
type Manager interface {
|
type Manager interface {
|
||||||
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error
|
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error
|
||||||
SetRouteChangeListener(listener RouteListener)
|
SetRouteChangeListener(listener RouteListener)
|
||||||
|
InitialRouteRange() []string
|
||||||
Stop()
|
Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +52,6 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
|
|||||||
if runtime.GOOS == "android" {
|
if runtime.GOOS == "android" {
|
||||||
cr := dm.clientRoutes(initialRoutes)
|
cr := dm.clientRoutes(initialRoutes)
|
||||||
dm.notifier.setInitialClientRoutes(cr)
|
dm.notifier.setInitialClientRoutes(cr)
|
||||||
networks := readRouteNetworks(cr)
|
|
||||||
|
|
||||||
// make sense to call before create interface
|
|
||||||
wgInterface.SetInitialRoutes(networks)
|
|
||||||
}
|
}
|
||||||
return dm
|
return dm
|
||||||
}
|
}
|
||||||
@@ -94,6 +91,11 @@ func (m *DefaultManager) SetRouteChangeListener(listener RouteListener) {
|
|||||||
m.notifier.setListener(listener)
|
m.notifier.setListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitialRouteRange return the list of initial routes. It used by mobile systems
|
||||||
|
func (m *DefaultManager) InitialRouteRange() []string {
|
||||||
|
return m.notifier.initialRouteRanges()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *DefaultManager) updateClientNetworks(updateSerial uint64, networks map[string][]*route.Route) {
|
func (m *DefaultManager) updateClientNetworks(updateSerial uint64, networks map[string][]*route.Route) {
|
||||||
// removing routes that do not exist as per the update from the Management service.
|
// removing routes that do not exist as per the update from the Management service.
|
||||||
for id, client := range m.clientNetworks {
|
for id, client := range m.clientNetworks {
|
||||||
@@ -163,11 +165,3 @@ func (m *DefaultManager) clientRoutes(initialRoutes []*route.Route) []*route.Rou
|
|||||||
}
|
}
|
||||||
return rs
|
return rs
|
||||||
}
|
}
|
||||||
|
|
||||||
func readRouteNetworks(cr []*route.Route) []string {
|
|
||||||
routesNetworks := make([]string, 0)
|
|
||||||
for _, r := range cr {
|
|
||||||
routesNetworks = append(routesNetworks, r.Network.String())
|
|
||||||
}
|
|
||||||
return routesNetworks
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ type MockManager struct {
|
|||||||
StopFunc func()
|
StopFunc func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitialClientRoutesNetworks mock implementation of InitialClientRoutesNetworks from Manager interface
|
// InitialRouteRange mock implementation of InitialRouteRange from Manager interface
|
||||||
func (m *MockManager) InitialClientRoutesNetworks() []string {
|
func (m *MockManager) InitialRouteRange() []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,3 +84,7 @@ func (n *notifier) hasDiff(a []string, b []string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *notifier) initialRouteRanges() []string {
|
||||||
|
return n.initialRouteRangers
|
||||||
|
}
|
||||||
|
|||||||
82
client/internal/routemanager/systemops_bsd.go
Normal file
82
client/internal/routemanager/systemops_bsd.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/net/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
// selected BSD Route flags.
|
||||||
|
const (
|
||||||
|
RTF_UP = 0x1
|
||||||
|
RTF_GATEWAY = 0x2
|
||||||
|
RTF_HOST = 0x4
|
||||||
|
RTF_REJECT = 0x8
|
||||||
|
RTF_DYNAMIC = 0x10
|
||||||
|
RTF_MODIFIED = 0x20
|
||||||
|
RTF_STATIC = 0x800
|
||||||
|
RTF_BLACKHOLE = 0x1000
|
||||||
|
RTF_LOCAL = 0x200000
|
||||||
|
RTF_BROADCAST = 0x400000
|
||||||
|
RTF_MULTICAST = 0x800000
|
||||||
|
)
|
||||||
|
|
||||||
|
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
||||||
|
tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
msgs, err := route.ParseRIB(route.RIBTypeRoute, tab)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
m := msg.(*route.RouteMessage)
|
||||||
|
|
||||||
|
if m.Version < 3 || m.Version > 5 {
|
||||||
|
return false, fmt.Errorf("unexpected RIB message version: %d", m.Version)
|
||||||
|
}
|
||||||
|
if m.Type != 4 /* RTM_GET */ {
|
||||||
|
return true, fmt.Errorf("unexpected RIB message type: %d", m.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Flags&RTF_UP == 0 ||
|
||||||
|
m.Flags&(RTF_REJECT|RTF_BLACKHOLE) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dst, err := toIPAddr(m.Addrs[0])
|
||||||
|
if err != nil {
|
||||||
|
return true, fmt.Errorf("unexpected RIB destination: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mask, _ := toIPAddr(m.Addrs[2])
|
||||||
|
cidr, _ := net.IPMask(mask.To4()).Size()
|
||||||
|
if dst.String() == prefix.Addr().String() && cidr == prefix.Bits() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toIPAddr(a route.Addr) (net.IP, error) {
|
||||||
|
switch t := a.(type) {
|
||||||
|
case *route.Inet4Addr:
|
||||||
|
ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
|
||||||
|
return ip, nil
|
||||||
|
case *route.Inet6Addr:
|
||||||
|
ip := make(net.IP, net.IPv6len)
|
||||||
|
copy(ip, t.IP[:])
|
||||||
|
return ip, nil
|
||||||
|
default:
|
||||||
|
return net.IP{}, fmt.Errorf("unknown family: %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,28 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Pulled from http://man7.org/linux/man-pages/man7/rtnetlink.7.html
|
||||||
|
// See the section on RTM_NEWROUTE, specifically 'struct rtmsg'.
|
||||||
|
type routeInfoInMemory struct {
|
||||||
|
Family byte
|
||||||
|
DstLen byte
|
||||||
|
SrcLen byte
|
||||||
|
TOS byte
|
||||||
|
|
||||||
|
Table byte
|
||||||
|
Protocol byte
|
||||||
|
Scope byte
|
||||||
|
Type byte
|
||||||
|
|
||||||
|
Flags uint32
|
||||||
|
}
|
||||||
|
|
||||||
const ipv4ForwardingPath = "/proc/sys/net/ipv4/ip_forward"
|
const ipv4ForwardingPath = "/proc/sys/net/ipv4/ip_forward"
|
||||||
|
|
||||||
func addToRouteTable(prefix netip.Prefix, addr string) error {
|
func addToRouteTable(prefix netip.Prefix, addr string) error {
|
||||||
@@ -61,6 +79,45 @@ func removeFromRouteTable(prefix netip.Prefix) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
||||||
|
tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
msgs, err := syscall.ParseNetlinkMessage(tab)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
loop:
|
||||||
|
for _, m := range msgs {
|
||||||
|
switch m.Header.Type {
|
||||||
|
case syscall.NLMSG_DONE:
|
||||||
|
break loop
|
||||||
|
case syscall.RTM_NEWROUTE:
|
||||||
|
rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0]))
|
||||||
|
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if rt.Family != syscall.AF_INET {
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attr := range attrs {
|
||||||
|
if attr.Attr.Type == syscall.RTA_DST {
|
||||||
|
ip := net.IP(attr.Value)
|
||||||
|
mask := net.CIDRMask(int(rt.DstLen), len(attr.Value)*8)
|
||||||
|
cidr, _ := mask.Size()
|
||||||
|
if ip.String() == prefix.Addr().String() && cidr == prefix.Bits() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func enableIPForwarding() error {
|
func enableIPForwarding() error {
|
||||||
bytes, err := os.ReadFile(ipv4ForwardingPath)
|
bytes, err := os.ReadFile(ipv4ForwardingPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,19 +14,26 @@ import (
|
|||||||
var errRouteNotFound = fmt.Errorf("route not found")
|
var errRouteNotFound = fmt.Errorf("route not found")
|
||||||
|
|
||||||
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
|
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
|
||||||
gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
||||||
if err != nil && err != errRouteNotFound {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
prefixGateway, err := getExistingRIBRouteGateway(prefix)
|
|
||||||
if err != nil && err != errRouteNotFound {
|
if err != nil && err != errRouteNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefixGateway != nil && !prefixGateway.Equal(gateway) {
|
gatewayIP := netip.MustParseAddr(defaultGateway.String())
|
||||||
log.Warnf("skipping adding a new route for network %s because it already exists and is pointing to the non default gateway: %s", prefix, prefixGateway)
|
if prefix.Contains(gatewayIP) {
|
||||||
|
log.Warnf("skipping adding a new route for network %s because it overlaps with the default gateway: %s", prefix, gatewayIP)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok, err := existsInRouteTable(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
log.Warnf("skipping adding a new route for network %s because it already exists", prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return addToRouteTable(prefix, addr)
|
return addToRouteTable(prefix, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +60,7 @@ func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) {
|
|||||||
log.Errorf("getting routes returned an error: %v", err)
|
log.Errorf("getting routes returned an error: %v", err)
|
||||||
return nil, errRouteNotFound
|
return nil, errRouteNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if gateway == nil {
|
if gateway == nil {
|
||||||
return preferredSrc, nil
|
return preferredSrc, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
"github.com/pion/transport/v2/stdnet"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pion/transport/v2/stdnet"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddRemoveRoutes(t *testing.T) {
|
func TestAddRemoveRoutes(t *testing.T) {
|
||||||
@@ -114,3 +120,98 @@ func TestGetExistingRIBRouteGateway(t *testing.T) {
|
|||||||
t.Fatalf("local ip should match with testing IP: want %s got %s", testingIP, localIP.String())
|
t.Fatalf("local ip should match with testing IP: want %s got %s", testingIP, localIP.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) {
|
||||||
|
defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
||||||
|
fmt.Println("defaultGateway: ", defaultGateway)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
prefix netip.Prefix
|
||||||
|
preExistingPrefix netip.Prefix
|
||||||
|
shouldAddRoute bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Add And Remove random Route",
|
||||||
|
prefix: netip.MustParsePrefix("99.99.99.99/32"),
|
||||||
|
shouldAddRoute: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Add Route if overlaps with default gateway",
|
||||||
|
prefix: netip.MustParsePrefix(defaultGateway.String() + "/31"),
|
||||||
|
shouldAddRoute: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Add Route if bigger network exists",
|
||||||
|
prefix: netip.MustParsePrefix("100.100.100.0/24"),
|
||||||
|
preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"),
|
||||||
|
shouldAddRoute: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Add Route if smaller network exists",
|
||||||
|
prefix: netip.MustParsePrefix("100.100.0.0/16"),
|
||||||
|
preExistingPrefix: netip.MustParsePrefix("100.100.100.0/24"),
|
||||||
|
shouldAddRoute: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Add Route if same network exists",
|
||||||
|
prefix: netip.MustParsePrefix("100.100.0.0/16"),
|
||||||
|
preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"),
|
||||||
|
shouldAddRoute: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, testCase := range testCases {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
defer func() {
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
}()
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", iface.DefaultMTU, nil, newNet)
|
||||||
|
require.NoError(t, err, "should create testing WGIface interface")
|
||||||
|
defer wgInterface.Close()
|
||||||
|
|
||||||
|
err = wgInterface.Create()
|
||||||
|
require.NoError(t, err, "should create testing wireguard interface")
|
||||||
|
|
||||||
|
MockAddr := wgInterface.Address().IP.String()
|
||||||
|
|
||||||
|
// Prepare the environment
|
||||||
|
if testCase.preExistingPrefix.IsValid() {
|
||||||
|
err := addToRouteTableIfNoExists(testCase.preExistingPrefix, MockAddr)
|
||||||
|
require.NoError(t, err, "should not return err when adding pre-existing route")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the route
|
||||||
|
err = addToRouteTableIfNoExists(testCase.prefix, MockAddr)
|
||||||
|
require.NoError(t, err, "should not return err when adding route")
|
||||||
|
|
||||||
|
if testCase.shouldAddRoute {
|
||||||
|
// test if route exists after adding
|
||||||
|
ok, err := existsInRouteTable(testCase.prefix)
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
require.True(t, ok, "route should exist")
|
||||||
|
|
||||||
|
// remove route again if added
|
||||||
|
err = removeFromRouteTableIfNonSystem(testCase.prefix, MockAddr)
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// route should either not have been added or should have been removed
|
||||||
|
// In case of already existing route, it should not have been added (but still exist)
|
||||||
|
ok, err := existsInRouteTable(testCase.prefix)
|
||||||
|
fmt.Println("Buffer string: ", buf.String())
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
if !strings.Contains(buf.String(), "because it already exists") {
|
||||||
|
require.False(t, ok, "route should not exist")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
37
client/internal/routemanager/systemops_windows.go
Normal file
37
client/internal/routemanager/systemops_windows.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/yusufpapurcu/wmi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Win32_IP4RouteTable struct {
|
||||||
|
Destination string
|
||||||
|
Mask string
|
||||||
|
}
|
||||||
|
|
||||||
|
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
||||||
|
var routes []Win32_IP4RouteTable
|
||||||
|
query := "SELECT Destination, Mask FROM Win32_IP4RouteTable"
|
||||||
|
|
||||||
|
err := wmi.Query(query, &routes)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
ip := net.ParseIP(route.Mask)
|
||||||
|
ip = ip.To4()
|
||||||
|
mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3])
|
||||||
|
cidr, _ := mask.Size()
|
||||||
|
if route.Destination == prefix.Addr().String() && cidr == prefix.Bits() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
23
go.mod
23
go.mod
@@ -17,12 +17,12 @@ require (
|
|||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.9.0
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.8.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
google.golang.org/grpc v1.52.3
|
google.golang.org/grpc v1.55.0
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.30.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
)
|
)
|
||||||
@@ -57,25 +57,31 @@ require (
|
|||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.3.0
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3
|
||||||
go.opentelemetry.io/otel v1.11.1
|
go.opentelemetry.io/otel v1.11.1
|
||||||
go.opentelemetry.io/otel/exporters/prometheus v0.33.0
|
go.opentelemetry.io/otel/exporters/prometheus v0.33.0
|
||||||
go.opentelemetry.io/otel/metric v0.33.0
|
go.opentelemetry.io/otel/metric v0.33.0
|
||||||
go.opentelemetry.io/otel/sdk/metric v0.33.0
|
go.opentelemetry.io/otel/sdk/metric v0.33.0
|
||||||
goauthentik.io/api/v3 v3.2023051.3
|
goauthentik.io/api/v3 v3.2023051.3
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.10.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/oauth2 v0.8.0
|
||||||
|
golang.org/x/sync v0.2.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.8.0
|
||||||
|
google.golang.org/api v0.126.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go/compute v1.19.3 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
@@ -91,9 +97,14 @@ require (
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/google/s2a-go v0.1.4 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/native v1.0.0 // indirect
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
@@ -119,17 +130,17 @@ require (
|
|||||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
github.com/yuin/goldmark v1.4.13 // indirect
|
github.com/yuin/goldmark v1.4.13 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.11.1 // indirect
|
go.opentelemetry.io/otel/sdk v1.11.1 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
||||||
golang.org/x/image v0.5.0 // indirect
|
golang.org/x/image v0.5.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/oauth2 v0.8.0 // indirect
|
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
|
|||||||
47
go.sum
47
go.sum
@@ -20,7 +20,11 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
|
|||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
|
||||||
|
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
|
||||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
@@ -87,8 +91,9 @@ github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
|
|||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -101,6 +106,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
|||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
@@ -163,6 +169,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
@@ -219,6 +226,7 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
|||||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
@@ -250,12 +258,14 @@ github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBs
|
|||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
@@ -321,13 +331,19 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||||
|
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||||
github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.10.0 h1:ebSgKfMxynOdxw8QQuFOKMgomqeLGPqNLQox2bo42zg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw=
|
||||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||||
github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||||
@@ -661,6 +677,8 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
@@ -668,6 +686,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
|
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
|
||||||
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
|
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
|
||||||
go.opentelemetry.io/otel/exporters/prometheus v0.33.0 h1:xXhPj7SLKWU5/Zd4Hxmd+X1C4jdmvc0Xy+kvjFx2z60=
|
go.opentelemetry.io/otel/exporters/prometheus v0.33.0 h1:xXhPj7SLKWU5/Zd4Hxmd+X1C4jdmvc0Xy+kvjFx2z60=
|
||||||
@@ -697,10 +717,11 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -730,6 +751,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
|
|||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
@@ -830,8 +852,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -948,6 +971,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
@@ -1040,6 +1064,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o=
|
||||||
|
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@@ -1081,8 +1107,10 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
|
|||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c=
|
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
|
||||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@@ -1101,9 +1129,10 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
|||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||||
|
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||||
google.golang.org/grpc v1.51.0-dev/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
google.golang.org/grpc v1.51.0-dev/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||||
google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ type PacketFilter interface {
|
|||||||
// DropIncoming filter incoming packets from external sources to host
|
// DropIncoming filter incoming packets from external sources to host
|
||||||
DropIncoming(packetData []byte) bool
|
DropIncoming(packetData []byte) bool
|
||||||
|
|
||||||
|
// AddUDPPacketHook calls hook when UDP packet from given direction matched
|
||||||
|
//
|
||||||
|
// Hook function returns flag which indicates should be the matched package dropped or not.
|
||||||
|
// Hook function receives raw network packet data as argument.
|
||||||
|
AddUDPPacketHook(in bool, ip net.IP, dPort uint16, hook func(packet []byte) bool) string
|
||||||
|
|
||||||
|
// RemovePacketHook removes hook by ID
|
||||||
|
RemovePacketHook(hookID string) error
|
||||||
|
|
||||||
// SetNetwork of the wireguard interface to which filtering applied
|
// SetNetwork of the wireguard interface to which filtering applied
|
||||||
SetNetwork(*net.IPNet)
|
SetNetwork(*net.IPNet)
|
||||||
}
|
}
|
||||||
@@ -82,8 +91,8 @@ func (d *DeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFiltering sets packet filter to device
|
// SetFilter sets packet filter to device
|
||||||
func (d *DeviceWrapper) SetFiltering(filter PacketFilter) {
|
func (d *DeviceWrapper) SetFilter(filter PacketFilter) {
|
||||||
d.mutex.Lock()
|
d.mutex.Lock()
|
||||||
d.filter = filter
|
d.filter = filter
|
||||||
d.mutex.Unlock()
|
d.mutex.Unlock()
|
||||||
|
|||||||
@@ -14,13 +14,6 @@ func TestDeviceWrapperRead(t *testing.T) {
|
|||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
|
|
||||||
tun := mocks.NewMockDevice(ctrl)
|
|
||||||
filter := mocks.NewMockPacketFilter(ctrl)
|
|
||||||
|
|
||||||
mockBufs := [][]byte{{}}
|
|
||||||
mockSizes := []int{0}
|
|
||||||
mockOffset := 0
|
|
||||||
|
|
||||||
t.Run("read ICMP", func(t *testing.T) {
|
t.Run("read ICMP", func(t *testing.T) {
|
||||||
ipLayer := &layers.IPv4{
|
ipLayer := &layers.IPv4{
|
||||||
Version: 4,
|
Version: 4,
|
||||||
@@ -46,6 +39,11 @@ func TestDeviceWrapperRead(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mockBufs := [][]byte{{}}
|
||||||
|
mockSizes := []int{0}
|
||||||
|
mockOffset := 0
|
||||||
|
|
||||||
|
tun := mocks.NewMockDevice(ctrl)
|
||||||
tun.EXPECT().Read(mockBufs, mockSizes, mockOffset).
|
tun.EXPECT().Read(mockBufs, mockSizes, mockOffset).
|
||||||
DoAndReturn(func(bufs [][]byte, sizes []int, offset int) (int, error) {
|
DoAndReturn(func(bufs [][]byte, sizes []int, offset int) (int, error) {
|
||||||
bufs[0] = buffer.Bytes()
|
bufs[0] = buffer.Bytes()
|
||||||
@@ -95,7 +93,10 @@ func TestDeviceWrapperRead(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mockBufs := [][]byte{buffer.Bytes()}
|
||||||
|
|
||||||
mockBufs[0] = buffer.Bytes()
|
mockBufs[0] = buffer.Bytes()
|
||||||
|
tun := mocks.NewMockDevice(ctrl)
|
||||||
tun.EXPECT().Write(mockBufs, 0).Return(1, nil)
|
tun.EXPECT().Write(mockBufs, 0).Return(1, nil)
|
||||||
|
|
||||||
wrapped := newDeviceWrapper(tun)
|
wrapped := newDeviceWrapper(tun)
|
||||||
@@ -138,10 +139,13 @@ func TestDeviceWrapperRead(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mockBufs = [][]byte{}
|
mockBufs := [][]byte{}
|
||||||
|
|
||||||
|
tun := mocks.NewMockDevice(ctrl)
|
||||||
tun.EXPECT().Write(mockBufs, 0).Return(0, nil)
|
tun.EXPECT().Write(mockBufs, 0).Return(0, nil)
|
||||||
filter.EXPECT().DropOutput(gomock.Any()).Return(true)
|
|
||||||
|
filter := mocks.NewMockPacketFilter(ctrl)
|
||||||
|
filter.EXPECT().DropIncoming(gomock.Any()).Return(true)
|
||||||
|
|
||||||
wrapped := newDeviceWrapper(tun)
|
wrapped := newDeviceWrapper(tun)
|
||||||
wrapped.filter = filter
|
wrapped.filter = filter
|
||||||
@@ -188,13 +192,15 @@ func TestDeviceWrapperRead(t *testing.T) {
|
|||||||
mockSizes := []int{0}
|
mockSizes := []int{0}
|
||||||
mockOffset := 0
|
mockOffset := 0
|
||||||
|
|
||||||
|
tun := mocks.NewMockDevice(ctrl)
|
||||||
tun.EXPECT().Read(mockBufs, mockSizes, mockOffset).
|
tun.EXPECT().Read(mockBufs, mockSizes, mockOffset).
|
||||||
DoAndReturn(func(bufs [][]byte, sizes []int, offset int) (int, error) {
|
DoAndReturn(func(bufs [][]byte, sizes []int, offset int) (int, error) {
|
||||||
bufs[0] = buffer.Bytes()
|
bufs[0] = buffer.Bytes()
|
||||||
sizes[0] = len(bufs[0])
|
sizes[0] = len(bufs[0])
|
||||||
return 1, nil
|
return 1, nil
|
||||||
})
|
})
|
||||||
filter.EXPECT().DropInput(gomock.Any()).Return(true)
|
filter := mocks.NewMockPacketFilter(ctrl)
|
||||||
|
filter.EXPECT().DropOutgoing(gomock.Any()).Return(true)
|
||||||
|
|
||||||
wrapped := newDeviceWrapper(tun)
|
wrapped := newDeviceWrapper(tun)
|
||||||
wrapped.filter = filter
|
wrapped.filter = filter
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type WGIface struct {
|
|||||||
configurer wGConfigurer
|
configurer wGConfigurer
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
userspaceBind bool
|
userspaceBind bool
|
||||||
|
filter PacketFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUserspaceBind indicates whether this interfaces is userspace with bind.ICEBind
|
// IsUserspaceBind indicates whether this interfaces is userspace with bind.ICEBind
|
||||||
@@ -35,15 +36,6 @@ func (w *WGIface) GetBind() *bind.ICEBind {
|
|||||||
return w.tun.iceBind
|
return w.tun.iceBind
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new Wireguard interface, sets a given IP and brings it up.
|
|
||||||
// Will reuse an existing one.
|
|
||||||
func (w *WGIface) Create() error {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
log.Debugf("create WireGuard interface %s", w.tun.DeviceName())
|
|
||||||
return w.tun.Create()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the interface name
|
// Name returns the interface name
|
||||||
func (w *WGIface) Name() string {
|
func (w *WGIface) Name() string {
|
||||||
return w.tun.DeviceName()
|
return w.tun.DeviceName()
|
||||||
@@ -120,8 +112,8 @@ func (w *WGIface) Close() error {
|
|||||||
return w.tun.Close()
|
return w.tun.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFiltering sets packet filters for the userspace impelemntation
|
// SetFilter sets packet filters for the userspace impelemntation
|
||||||
func (w *WGIface) SetFiltering(filter PacketFilter) error {
|
func (w *WGIface) SetFilter(filter PacketFilter) error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
@@ -129,7 +121,25 @@ func (w *WGIface) SetFiltering(filter PacketFilter) error {
|
|||||||
return fmt.Errorf("userspace packet filtering not handled on this device")
|
return fmt.Errorf("userspace packet filtering not handled on this device")
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.SetNetwork(w.tun.address.Network)
|
w.filter = filter
|
||||||
w.tun.wrapper.SetFiltering(filter)
|
w.filter.SetNetwork(w.tun.address.Network)
|
||||||
|
|
||||||
|
w.tun.wrapper.SetFilter(filter)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFilter returns packet filter used by interface if it uses userspace device implementation
|
||||||
|
func (w *WGIface) GetFilter() PacketFilter {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
return w.filter
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevice to interact with raw device (with filtering)
|
||||||
|
func (w *WGIface) GetDevice() *DeviceWrapper {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
return w.tun.wrapper
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/transport/v2"
|
"github.com/pion/transport/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
@@ -27,7 +29,16 @@ func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter
|
|||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInitialRoutes store the given routes and on the tun creation will be used
|
// CreateOnMobile creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
func (w *WGIface) SetInitialRoutes(routes []string) {
|
// Will reuse an existing one.
|
||||||
w.tun.SetRoutes(routes)
|
func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
log.Debugf("create WireGuard interface %s", w.tun.DeviceName())
|
||||||
|
return w.tun.Create(mIFaceArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create this function make sense on mobile only
|
||||||
|
func (w *WGIface) Create() error {
|
||||||
|
return fmt.Errorf("this function has not implemented on mobile")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/transport/v2"
|
"github.com/pion/transport/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
@@ -26,7 +28,16 @@ func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter
|
|||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInitialRoutes unused function on non Android
|
// CreateOnMobile this function make sense on mobile only
|
||||||
func (w *WGIface) SetInitialRoutes(routes []string) {
|
func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error {
|
||||||
|
return fmt.Errorf("this function has not implemented on non mobile")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
|
// Will reuse an existing one.
|
||||||
|
func (w *WGIface) Create() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
log.Debugf("create WireGuard interface %s", w.tun.DeviceName())
|
||||||
|
return w.tun.Create()
|
||||||
}
|
}
|
||||||
|
|||||||
7
iface/mocks/README.md
Normal file
7
iface/mocks/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Mocks
|
||||||
|
|
||||||
|
To generate (or refresh) mocks from iface package interfaces please install [mockgen](https://github.com/golang/mock).
|
||||||
|
Run this command to update PacketFilter mock:
|
||||||
|
```bash
|
||||||
|
mockgen -destination iface/mocks/filter.go -package mocks github.com/netbirdio/netbird/iface PacketFilter
|
||||||
|
```
|
||||||
@@ -34,21 +34,21 @@ func (m *MockPacketFilter) EXPECT() *MockPacketFilterMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// DropInput mocks base method.
|
// AddUDPPacketHook mocks base method.
|
||||||
func (m *MockPacketFilter) DropOutgoing(arg0 []byte) bool {
|
func (m *MockPacketFilter) AddUDPPacketHook(arg0 bool, arg1 net.IP, arg2 uint16, arg3 func([]byte) bool) string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DropOutgoing", arg0)
|
ret := m.ctrl.Call(m, "AddUDPPacketHook", arg0, arg1, arg2, arg3)
|
||||||
ret0, _ := ret[0].(bool)
|
ret0, _ := ret[0].(string)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DropInput indicates an expected call of DropInput.
|
// AddUDPPacketHook indicates an expected call of AddUDPPacketHook.
|
||||||
func (mr *MockPacketFilterMockRecorder) DropInput(arg0 interface{}) *gomock.Call {
|
func (mr *MockPacketFilterMockRecorder) AddUDPPacketHook(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUDPPacketHook", reflect.TypeOf((*MockPacketFilter)(nil).AddUDPPacketHook), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DropOutput mocks base method.
|
// DropIncoming mocks base method.
|
||||||
func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
|
func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DropIncoming", arg0)
|
ret := m.ctrl.Call(m, "DropIncoming", arg0)
|
||||||
@@ -56,12 +56,40 @@ func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DropOutput indicates an expected call of DropOutput.
|
// DropIncoming indicates an expected call of DropIncoming.
|
||||||
func (mr *MockPacketFilterMockRecorder) DropOutput(arg0 interface{}) *gomock.Call {
|
func (mr *MockPacketFilterMockRecorder) DropIncoming(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DropOutgoing mocks base method.
|
||||||
|
func (m *MockPacketFilter) DropOutgoing(arg0 []byte) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DropOutgoing", arg0)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropOutgoing indicates an expected call of DropOutgoing.
|
||||||
|
func (mr *MockPacketFilterMockRecorder) DropOutgoing(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePacketHook mocks base method.
|
||||||
|
func (m *MockPacketFilter) RemovePacketHook(arg0 string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RemovePacketHook", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePacketHook indicates an expected call of RemovePacketHook.
|
||||||
|
func (mr *MockPacketFilterMockRecorder) RemovePacketHook(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePacketHook", reflect.TypeOf((*MockPacketFilter)(nil).RemovePacketHook), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// SetNetwork mocks base method.
|
// SetNetwork mocks base method.
|
||||||
func (m *MockPacketFilter) SetNetwork(arg0 *net.IPNet) {
|
func (m *MockPacketFilter) SetNetwork(arg0 *net.IPNet) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
87
iface/mocks/iface/mocks/filter.go
Normal file
87
iface/mocks/iface/mocks/filter.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/netbirdio/netbird/iface (interfaces: PacketFilter)
|
||||||
|
|
||||||
|
// Package mocks is a generated GoMock package.
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
net "net"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockPacketFilter is a mock of PacketFilter interface.
|
||||||
|
type MockPacketFilter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockPacketFilterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockPacketFilterMockRecorder is the mock recorder for MockPacketFilter.
|
||||||
|
type MockPacketFilterMockRecorder struct {
|
||||||
|
mock *MockPacketFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockPacketFilter creates a new mock instance.
|
||||||
|
func NewMockPacketFilter(ctrl *gomock.Controller) *MockPacketFilter {
|
||||||
|
mock := &MockPacketFilter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockPacketFilterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockPacketFilter) EXPECT() *MockPacketFilterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUDPPacketHook mocks base method.
|
||||||
|
func (m *MockPacketFilter) AddUDPPacketHook(arg0 bool, arg1 net.IP, arg2 uint16, arg3 func(*net.UDPAddr, []byte) bool) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddUDPPacketHook", arg0, arg1, arg2, arg3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUDPPacketHook indicates an expected call of AddUDPPacketHook.
|
||||||
|
func (mr *MockPacketFilterMockRecorder) AddUDPPacketHook(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUDPPacketHook", reflect.TypeOf((*MockPacketFilter)(nil).AddUDPPacketHook), arg0, arg1, arg2, arg3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropIncoming mocks base method.
|
||||||
|
func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DropIncoming", arg0)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropIncoming indicates an expected call of DropIncoming.
|
||||||
|
func (mr *MockPacketFilterMockRecorder) DropIncoming(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropOutgoing mocks base method.
|
||||||
|
func (m *MockPacketFilter) DropOutgoing(arg0 []byte) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DropOutgoing", arg0)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropOutgoing indicates an expected call of DropOutgoing.
|
||||||
|
func (mr *MockPacketFilterMockRecorder) DropOutgoing(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetwork mocks base method.
|
||||||
|
func (m *MockPacketFilter) SetNetwork(arg0 *net.IPNet) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "SetNetwork", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetwork indicates an expected call of SetNetwork.
|
||||||
|
func (mr *MockPacketFilterMockRecorder) SetNetwork(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNetwork", reflect.TypeOf((*MockPacketFilter)(nil).SetNetwork), arg0)
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
|
type MobileIFaceArguments struct {
|
||||||
|
Routes []string
|
||||||
|
Dns string
|
||||||
|
}
|
||||||
|
|
||||||
// NetInterface represents a generic network tunnel interface
|
// NetInterface represents a generic network tunnel interface
|
||||||
type NetInterface interface {
|
type NetInterface interface {
|
||||||
Close() error
|
Close() error
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ package iface
|
|||||||
|
|
||||||
// TunAdapter is an interface for create tun device from externel service
|
// TunAdapter is an interface for create tun device from externel service
|
||||||
type TunAdapter interface {
|
type TunAdapter interface {
|
||||||
ConfigureInterface(address string, mtu int, routes string) (int, error)
|
ConfigureInterface(address string, mtu int, dns string, routes string) (int, error)
|
||||||
UpdateAddr(address string) error
|
UpdateAddr(address string) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,12 @@ import (
|
|||||||
type tunDevice struct {
|
type tunDevice struct {
|
||||||
address WGAddress
|
address WGAddress
|
||||||
mtu int
|
mtu int
|
||||||
routes []string
|
|
||||||
tunAdapter TunAdapter
|
tunAdapter TunAdapter
|
||||||
|
iceBind *bind.ICEBind
|
||||||
|
|
||||||
fd int
|
fd int
|
||||||
name string
|
name string
|
||||||
device *device.Device
|
device *device.Device
|
||||||
iceBind *bind.ICEBind
|
|
||||||
wrapper *DeviceWrapper
|
wrapper *DeviceWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,14 +33,10 @@ func newTunDevice(address WGAddress, mtu int, tunAdapter TunAdapter, transportNe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tunDevice) SetRoutes(routes []string) {
|
func (t *tunDevice) Create(mIFaceArgs MobileIFaceArguments) error {
|
||||||
t.routes = routes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDevice) Create() error {
|
|
||||||
var err error
|
var err error
|
||||||
routesString := t.routesToString()
|
routesString := t.routesToString(mIFaceArgs.Routes)
|
||||||
t.fd, err = t.tunAdapter.ConfigureInterface(t.address.String(), t.mtu, routesString)
|
t.fd, err = t.tunAdapter.ConfigureInterface(t.address.String(), t.mtu, mIFaceArgs.Dns, routesString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to create Android interface: %s", err)
|
log.Errorf("failed to create Android interface: %s", err)
|
||||||
return err
|
return err
|
||||||
@@ -95,6 +90,6 @@ func (t *tunDevice) Close() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tunDevice) routesToString() string {
|
func (t *tunDevice) routesToString(routes []string) string {
|
||||||
return strings.Join(t.routes, ";")
|
return strings.Join(routes, ";")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
|||||||
# exports
|
# exports
|
||||||
export NETBIRD_DOMAIN
|
export NETBIRD_DOMAIN
|
||||||
export NETBIRD_AUTH_CLIENT_ID
|
export NETBIRD_AUTH_CLIENT_ID
|
||||||
|
export NETBIRD_AUTH_CLIENT_SECRET
|
||||||
export NETBIRD_AUTH_AUDIENCE
|
export NETBIRD_AUTH_AUDIENCE
|
||||||
export NETBIRD_AUTH_AUTHORITY
|
export NETBIRD_AUTH_AUTHORITY
|
||||||
export NETBIRD_USE_AUTH0
|
export NETBIRD_USE_AUTH0
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
# OIDC
|
# OIDC
|
||||||
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
|
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
||||||
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
- USE_AUTH0=$NETBIRD_USE_AUTH0
|
- USE_AUTH0=$NETBIRD_USE_AUTH0
|
||||||
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
# OIDC
|
# OIDC
|
||||||
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
|
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
||||||
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
- USE_AUTH0=$NETBIRD_USE_AUTH0
|
- USE_AUTH0=$NETBIRD_USE_AUTH0
|
||||||
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
|||||||
NETBIRD_AUTH_AUDIENCE=""
|
NETBIRD_AUTH_AUDIENCE=""
|
||||||
# e.g. netbird-client
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH_CLIENT_ID=""
|
NETBIRD_AUTH_CLIENT_ID=""
|
||||||
|
NETBIRD_AUTH_CLIENT_SECRET=""
|
||||||
# if you want to use a custom claim for the user ID instead of 'sub', set it here
|
# if you want to use a custom claim for the user ID instead of 'sub', set it here
|
||||||
# NETBIRD_AUTH_USER_ID_CLAIM=""
|
# NETBIRD_AUTH_USER_ID_CLAIM=""
|
||||||
# indicates whether to use Auth0 or not: true or false
|
# indicates whether to use Auth0 or not: true or false
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ NETBIRD_DOMAIN=$CI_NETBIRD_DOMAIN
|
|||||||
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://example.eu.auth0.com/.well-known/openid-configuration"
|
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://example.eu.auth0.com/.well-known/openid-configuration"
|
||||||
# e.g. netbird-client
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH_CLIENT_ID=$CI_NETBIRD_AUTH_CLIENT_ID
|
NETBIRD_AUTH_CLIENT_ID=$CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
|
NETBIRD_AUTH_CLIENT_SECRET=$CI_NETBIRD_AUTH_CLIENT_SECRET
|
||||||
# indicates whether to use Auth0 or not: true or false
|
# indicates whether to use Auth0 or not: true or false
|
||||||
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
||||||
NETBIRD_AUTH_AUDIENCE=$CI_NETBIRD_AUTH_AUDIENCE
|
NETBIRD_AUTH_AUDIENCE=$CI_NETBIRD_AUTH_AUDIENCE
|
||||||
|
|||||||
5
keepalive/client.go
Normal file
5
keepalive/client.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package keepalive
|
||||||
|
|
||||||
|
func IsKeepAliveMsg(body []byte) bool {
|
||||||
|
return len(body) == 0
|
||||||
|
}
|
||||||
155
keepalive/keep_alive.go
Normal file
155
keepalive/keep_alive.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package keepalive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GrpcVersionHeaderKey = "x-netbird-version"
|
||||||
|
|
||||||
|
reversProxyHeaderKey = "x-netbird-peer"
|
||||||
|
keepAliveInterval = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeepAlive struct {
|
||||||
|
sync.RWMutex
|
||||||
|
ticker *time.Ticker
|
||||||
|
done chan struct{}
|
||||||
|
streams map[string]*ioMonitor
|
||||||
|
keepAliveMsg interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeepAlive(keepAliveMsg interface{}) *KeepAlive {
|
||||||
|
ka := &KeepAlive{
|
||||||
|
ticker: time.NewTicker(1 * time.Second),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
streams: make(map[string]*ioMonitor),
|
||||||
|
keepAliveMsg: keepAliveMsg,
|
||||||
|
}
|
||||||
|
go ka.start()
|
||||||
|
return ka
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) StreamInterceptor() grpc.StreamServerInterceptor {
|
||||||
|
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
address, supported := k.keepAliveIsSupported(stream.Context())
|
||||||
|
if !supported {
|
||||||
|
return handler(srv, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &ioMonitor{
|
||||||
|
sync.Mutex{},
|
||||||
|
sync.Mutex{},
|
||||||
|
stream,
|
||||||
|
time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
k.addIoMonitor(address, m)
|
||||||
|
|
||||||
|
return handler(srv, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) UnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||||
|
address, supported := k.keepAliveIsSupported(ctx)
|
||||||
|
if supported {
|
||||||
|
k.updateLastSeen(address)
|
||||||
|
}
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) Stop() {
|
||||||
|
select {
|
||||||
|
case k.done <- struct{}{}:
|
||||||
|
k.ticker.Stop()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) start() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-k.done:
|
||||||
|
return
|
||||||
|
case t := <-k.ticker.C:
|
||||||
|
k.checkKeepAlive(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) checkKeepAlive(now time.Time) {
|
||||||
|
k.Lock()
|
||||||
|
defer k.Unlock()
|
||||||
|
for addr, m := range k.streams {
|
||||||
|
if k.isKeepAliveOutDated(now, m) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := k.sendKeepAlive(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("stop keepalive for: %s", addr)
|
||||||
|
delete(k.streams, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) keepAliveIsSupported(ctx context.Context) (string, bool) {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("metadata not found")
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
peerAddress := k.addressFromHeader(md)
|
||||||
|
if peerAddress == "" {
|
||||||
|
log.Debugf("peer is not using reverse proxy")
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(md.Get(GrpcVersionHeaderKey)) == 0 {
|
||||||
|
log.Debugf("version info not found")
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return peerAddress, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) addIoMonitor(address string, m *ioMonitor) {
|
||||||
|
k.Lock()
|
||||||
|
defer k.Unlock()
|
||||||
|
log.Debugf("add stream address for keepalive list: %s", address)
|
||||||
|
k.streams[address] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) sendKeepAlive(m *ioMonitor) error {
|
||||||
|
return m.sendMsg(k.keepAliveMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) updateLastSeen(address string) {
|
||||||
|
k.RLock()
|
||||||
|
m, ok := k.streams[address]
|
||||||
|
k.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.updateLastSeen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) addressFromHeader(md metadata.MD) string {
|
||||||
|
peer := md.Get(reversProxyHeaderKey)
|
||||||
|
if len(peer) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return peer[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeepAlive) isKeepAliveOutDated(now time.Time, m *ioMonitor) bool {
|
||||||
|
return now.Sub(m.getLastSeen()) < keepAliveInterval
|
||||||
|
}
|
||||||
35
keepalive/monitor.go
Normal file
35
keepalive/monitor.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package keepalive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ioMonitor struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
streamLock sync.Mutex
|
||||||
|
grpc.ServerStream
|
||||||
|
lastSeen time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ioMonitor) sendMsg(m interface{}) error {
|
||||||
|
l.updateLastSeen()
|
||||||
|
l.streamLock.Lock()
|
||||||
|
defer l.streamLock.Unlock()
|
||||||
|
return l.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ioMonitor) updateLastSeen() {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.lastSeen = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ioMonitor) getLastSeen() time.Time {
|
||||||
|
l.mu.Lock()
|
||||||
|
t := l.lastSeen
|
||||||
|
l.mu.Unlock()
|
||||||
|
return t
|
||||||
|
}
|
||||||
@@ -15,5 +15,5 @@ type Client interface {
|
|||||||
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||||
GetRoutes() ([]*proto.Route, error)
|
GetNetworkMap() (*proto.NetworkMap, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -23,7 +24,9 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
|
appKeepAlive "github.com/netbirdio/netbird/keepalive"
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnStateNotifier is a wrapper interface of the status recorders
|
// ConnStateNotifier is a wrapper interface of the status recorders
|
||||||
@@ -67,6 +70,9 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
|||||||
|
|
||||||
realClient := proto.NewManagementServiceClient(conn)
|
realClient := proto.NewManagementServiceClient(conn)
|
||||||
|
|
||||||
|
md := metadata.Pairs(appKeepAlive.GrpcVersionHeaderKey, version.NetbirdVersion())
|
||||||
|
ctx = metadata.NewOutgoingContext(ctx, md)
|
||||||
|
|
||||||
return &GrpcClient{
|
return &GrpcClient{
|
||||||
key: ourPrivateKey,
|
key: ourPrivateKey,
|
||||||
realClient: realClient,
|
realClient: realClient,
|
||||||
@@ -131,6 +137,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
|
|
||||||
ctx, cancelStream := context.WithCancel(c.ctx)
|
ctx, cancelStream := context.WithCancel(c.ctx)
|
||||||
defer cancelStream()
|
defer cancelStream()
|
||||||
|
|
||||||
stream, err := c.connectToStream(ctx, *serverPubKey)
|
stream, err := c.connectToStream(ctx, *serverPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to open Management Service stream: %s", err)
|
log.Debugf("failed to open Management Service stream: %s", err)
|
||||||
@@ -172,8 +179,8 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoutes return with the routes
|
// GetNetworkMap return with the network map
|
||||||
func (c *GrpcClient) GetRoutes() ([]*proto.Route, error) {
|
func (c *GrpcClient) GetNetworkMap() (*proto.NetworkMap, error) {
|
||||||
serverPubKey, err := c.GetServerPublicKey()
|
serverPubKey, err := c.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed getting Management Service public key: %s", err)
|
log.Debugf("failed getting Management Service public key: %s", err)
|
||||||
@@ -212,7 +219,7 @@ func (c *GrpcClient) GetRoutes() ([]*proto.Route, error) {
|
|||||||
return nil, fmt.Errorf("invalid msg, required network map")
|
return nil, fmt.Errorf("invalid msg, required network map")
|
||||||
}
|
}
|
||||||
|
|
||||||
return decryptedResp.GetNetworkMap().GetRoutes(), nil
|
return decryptedResp.GetNetworkMap(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GrpcClient) connectToStream(ctx context.Context, serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
func (c *GrpcClient) connectToStream(ctx context.Context, serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
||||||
@@ -246,6 +253,10 @@ func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, se
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if appKeepAlive.IsKeepAliveMsg(update.Body) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("got an update message from Management Service")
|
log.Debugf("got an update message from Management Service")
|
||||||
decryptedResp := &proto.SyncResponse{}
|
decryptedResp := &proto.SyncResponse{}
|
||||||
err = encryption.DecryptMessage(serverPubKey, c.key, update.Body, decryptedResp)
|
err = encryption.DecryptMessage(serverPubKey, c.key, update.Body, decryptedResp)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.D
|
|||||||
return m.GetDeviceAuthorizationFlowFunc(serverKey)
|
return m.GetDeviceAuthorizationFlowFunc(serverKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoutes mock implementation of GetRoutes from mgm.Client interface
|
// GetNetworkMap mock implementation of GetNetworkMap from mgm.Client interface
|
||||||
func (m *MockClient) GetRoutes() ([]*proto.Route, error) {
|
func (m *MockClient) GetNetworkMap() (*proto.NetworkMap, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
|
grpcKeepAlive "github.com/netbirdio/netbird/keepalive"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -202,11 +203,17 @@ var (
|
|||||||
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
|
||||||
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics)
|
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ka := grpcKeepAlive.NewKeepAlive(&mgmtProto.KeepAlive{})
|
||||||
|
defer ka.Stop()
|
||||||
|
sInterc := grpc.StreamInterceptor(ka.StreamInterceptor())
|
||||||
|
uInterc := grpc.UnaryInterceptor(ka.UnaryInterceptor())
|
||||||
|
gRPCOpts = append(gRPCOpts, sInterc, uInterc)
|
||||||
|
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
||||||
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
||||||
|
|
||||||
installationID, err := getInstallationID(store)
|
installationID, err := getInstallationID(store)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -329,3 +329,5 @@ message FirewallRule {
|
|||||||
ICMP = 4;
|
ICMP = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message KeepAlive {}
|
||||||
|
|||||||
@@ -1102,8 +1102,8 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
delete:
|
delete:
|
||||||
summary: Delete a User
|
summary: Block a User
|
||||||
description: Delete a user
|
description: This method blocks a user from accessing the system, but leaves the IDP user intact.
|
||||||
tags: [ Users ]
|
tags: [ Users ]
|
||||||
security:
|
security:
|
||||||
- BearerAuth: [ ]
|
- BearerAuth: [ ]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package http
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
@@ -175,8 +176,13 @@ func (h *Policies) savePolicy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.Ports != nil && len(*r.Ports) != 0 {
|
if r.Ports != nil && len(*r.Ports) != 0 {
|
||||||
ports := *r.Ports
|
for _, v := range *r.Ports {
|
||||||
pr.Ports = ports[:]
|
if port, err := strconv.Atoi(v); err != nil || port < 1 || port > 65535 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "valid port value is in 1..65535 range"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pr.Ports = append(pr.Ports, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate policy object
|
// validate policy object
|
||||||
|
|||||||
344
management/server/idp/google_workspace.go
Normal file
344
management/server/idp/google_workspace.go
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
admin "google.golang.org/api/admin/directory/v1"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoogleWorkspaceManager Google Workspace manager client instance.
|
||||||
|
type GoogleWorkspaceManager struct {
|
||||||
|
usersService *admin.UsersService
|
||||||
|
CustomerID string
|
||||||
|
httpClient ManagerHTTPClient
|
||||||
|
credentials ManagerCredentials
|
||||||
|
helper ManagerHelper
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleWorkspaceClientConfig Google Workspace manager client configurations.
|
||||||
|
type GoogleWorkspaceClientConfig struct {
|
||||||
|
ServiceAccountKey string
|
||||||
|
CustomerID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoogleWorkspaceCredentials Google Workspace authentication information.
|
||||||
|
type GoogleWorkspaceCredentials struct {
|
||||||
|
clientConfig GoogleWorkspaceClientConfig
|
||||||
|
helper ManagerHelper
|
||||||
|
httpClient ManagerHTTPClient
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GoogleWorkspaceCredentials) Authenticate() (JWTToken, error) {
|
||||||
|
return JWTToken{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGoogleWorkspaceManager creates a new instance of the GoogleWorkspaceManager.
|
||||||
|
func NewGoogleWorkspaceManager(config GoogleWorkspaceClientConfig, appMetrics telemetry.AppMetrics) (*GoogleWorkspaceManager, error) {
|
||||||
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
httpTransport.MaxIdleConns = 5
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
Transport: httpTransport,
|
||||||
|
}
|
||||||
|
helper := JsonParser{}
|
||||||
|
|
||||||
|
if config.CustomerID == "" {
|
||||||
|
return nil, fmt.Errorf("google IdP configuration is incomplete, CustomerID is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials := &GoogleWorkspaceCredentials{
|
||||||
|
clientConfig: config,
|
||||||
|
httpClient: httpClient,
|
||||||
|
helper: helper,
|
||||||
|
appMetrics: appMetrics,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Admin SDK Directory service client
|
||||||
|
adminCredentials, err := getGoogleCredentials(config.ServiceAccountKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := admin.NewService(context.Background(),
|
||||||
|
option.WithScopes(admin.AdminDirectoryUserScope, admin.AdminDirectoryUserschemaScope),
|
||||||
|
option.WithCredentials(adminCredentials),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = configureAppMetadataSchema(service, config.CustomerID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GoogleWorkspaceManager{
|
||||||
|
usersService: service.Users,
|
||||||
|
CustomerID: config.CustomerID,
|
||||||
|
httpClient: httpClient,
|
||||||
|
credentials: credentials,
|
||||||
|
helper: helper,
|
||||||
|
appMetrics: appMetrics,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
|
func (gm *GoogleWorkspaceManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
||||||
|
metadata, err := gm.helper.Marshal(appMetadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &admin.User{
|
||||||
|
CustomSchemas: map[string]googleapi.RawMessage{
|
||||||
|
"app_metadata": metadata,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = gm.usersService.Update(userID, user).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gm.appMetrics != nil {
|
||||||
|
gm.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserDataByID requests user data from Google Workspace via ID.
|
||||||
|
func (gm *GoogleWorkspaceManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
||||||
|
user, err := gm.usersService.Get(userID).Projection("full").Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gm.appMetrics != nil {
|
||||||
|
gm.appMetrics.IDPMetrics().CountGetUserDataByID()
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseGoogleWorkspaceUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccount returns all the users for a given profile.
|
||||||
|
func (gm *GoogleWorkspaceManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
|
query := fmt.Sprintf("app_metadata.wt_account_id=\"%s\"", accountID)
|
||||||
|
usersList, err := gm.usersService.List().Customer(gm.CustomerID).Query(query).Projection("full").Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usersData := make([]*UserData, 0)
|
||||||
|
for _, user := range usersList.Users {
|
||||||
|
userData, err := parseGoogleWorkspaceUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usersData = append(usersData, userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usersData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
|
// It returns a list of users indexed by accountID.
|
||||||
|
func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
|
usersList, err := gm.usersService.List().Customer(gm.CustomerID).Projection("full").Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gm.appMetrics != nil {
|
||||||
|
gm.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
indexedUsers := make(map[string][]*UserData)
|
||||||
|
for _, user := range usersList.Users {
|
||||||
|
userData, err := parseGoogleWorkspaceUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountID := userData.AppMetadata.WTAccountID
|
||||||
|
if accountID != "" {
|
||||||
|
if _, ok := indexedUsers[accountID]; !ok {
|
||||||
|
indexedUsers[accountID] = make([]*UserData, 0)
|
||||||
|
}
|
||||||
|
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexedUsers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser creates a new user in Google Workspace and sends an invitation.
|
||||||
|
func (gm *GoogleWorkspaceManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
|
||||||
|
invite := true
|
||||||
|
metadata := AppMetadata{
|
||||||
|
WTAccountID: accountID,
|
||||||
|
WTPendingInvite: &invite,
|
||||||
|
}
|
||||||
|
|
||||||
|
username := &admin.UserName{}
|
||||||
|
fields := strings.Fields(name)
|
||||||
|
if n := len(fields); n > 0 {
|
||||||
|
username.GivenName = strings.Join(fields[:n-1], " ")
|
||||||
|
username.FamilyName = fields[n-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := gm.helper.Marshal(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &admin.User{
|
||||||
|
Name: username,
|
||||||
|
PrimaryEmail: email,
|
||||||
|
CustomSchemas: map[string]googleapi.RawMessage{
|
||||||
|
"app_metadata": payload,
|
||||||
|
},
|
||||||
|
Password: GeneratePassword(8, 1, 1, 1),
|
||||||
|
}
|
||||||
|
user, err = gm.usersService.Insert(user).Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gm.appMetrics != nil {
|
||||||
|
gm.appMetrics.IDPMetrics().CountCreateUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseGoogleWorkspaceUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByEmail searches users with a given email.
|
||||||
|
// If no users have been found, this function returns an empty list.
|
||||||
|
func (gm *GoogleWorkspaceManager) GetUserByEmail(email string) ([]*UserData, error) {
|
||||||
|
user, err := gm.usersService.Get(email).Projection("full").Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gm.appMetrics != nil {
|
||||||
|
gm.appMetrics.IDPMetrics().CountGetUserByEmail()
|
||||||
|
}
|
||||||
|
|
||||||
|
userData, err := parseGoogleWorkspaceUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
users := make([]*UserData, 0)
|
||||||
|
users = append(users, userData)
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGoogleCredentials retrieves Google credentials based on the provided serviceAccountKey.
|
||||||
|
// It decodes the base64-encoded serviceAccountKey and attempts to obtain credentials using it.
|
||||||
|
// If that fails, it falls back to using the default Google credentials path.
|
||||||
|
// It returns the retrieved credentials or an error if unsuccessful.
|
||||||
|
func getGoogleCredentials(serviceAccountKey string) (*google.Credentials, error) {
|
||||||
|
log.Debug("retrieving google credentials from the base64 encoded service account key")
|
||||||
|
decodeKey, err := base64.StdEncoding.DecodeString(serviceAccountKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode service account key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := google.CredentialsFromJSON(
|
||||||
|
context.Background(),
|
||||||
|
decodeKey,
|
||||||
|
admin.AdminDirectoryUserschemaScope,
|
||||||
|
admin.AdminDirectoryUserScope,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
return creds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("failed to retrieve Google credentials from ServiceAccountKey: %v", err)
|
||||||
|
log.Debug("falling back to default google credentials location")
|
||||||
|
|
||||||
|
creds, err = google.FindDefaultCredentials(
|
||||||
|
context.Background(),
|
||||||
|
admin.AdminDirectoryUserschemaScope,
|
||||||
|
admin.AdminDirectoryUserScope,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureAppMetadataSchema create a custom schema for managing app metadata fields in Google Workspace.
|
||||||
|
func configureAppMetadataSchema(service *admin.Service, customerID string) error {
|
||||||
|
schemaList, err := service.Schemas.List(customerID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if app_metadata schema is already created
|
||||||
|
for _, schema := range schemaList.Schemas {
|
||||||
|
if schema.SchemaName == "app_metadata" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new app_metadata schema
|
||||||
|
appMetadataSchema := &admin.Schema{
|
||||||
|
SchemaName: "app_metadata",
|
||||||
|
Fields: []*admin.SchemaFieldSpec{
|
||||||
|
{
|
||||||
|
FieldName: "wt_account_id",
|
||||||
|
FieldType: "STRING",
|
||||||
|
MultiValued: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FieldName: "wt_pending_invite",
|
||||||
|
FieldType: "BOOL",
|
||||||
|
MultiValued: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = service.Schemas.Insert(customerID, appMetadataSchema).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGoogleWorkspaceUser parse google user to UserData.
|
||||||
|
func parseGoogleWorkspaceUser(user *admin.User) (*UserData, error) {
|
||||||
|
var appMetadata AppMetadata
|
||||||
|
|
||||||
|
// Get app metadata from custom schemas
|
||||||
|
if user.CustomSchemas != nil {
|
||||||
|
rawMessage := user.CustomSchemas["app_metadata"]
|
||||||
|
helper := JsonParser{}
|
||||||
|
|
||||||
|
if err := helper.Unmarshal(rawMessage, &appMetadata); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserData{
|
||||||
|
ID: user.Id,
|
||||||
|
Email: user.PrimaryEmail,
|
||||||
|
Name: user.Name.FullName,
|
||||||
|
AppMetadata: appMetadata,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -162,6 +162,12 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
APIToken: config.ExtraConfig["ApiToken"],
|
APIToken: config.ExtraConfig["ApiToken"],
|
||||||
}
|
}
|
||||||
return NewOktaManager(oktaClientConfig, appMetrics)
|
return NewOktaManager(oktaClientConfig, appMetrics)
|
||||||
|
case "google":
|
||||||
|
googleClientConfig := GoogleWorkspaceClientConfig{
|
||||||
|
ServiceAccountKey: config.ExtraConfig["ServiceAccountKey"],
|
||||||
|
CustomerID: config.ExtraConfig["CustomerId"],
|
||||||
|
}
|
||||||
|
return NewGoogleWorkspaceManager(googleClientConfig, appMetrics)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
||||||
|
|||||||
@@ -27,7 +27,18 @@ then
|
|||||||
echo "Please run: brew install netbirdio/tap/netbird"
|
echo "Please run: brew install netbirdio/tap/netbird"
|
||||||
echo "to update it"
|
echo "to update it"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "$NB_BIN" ]
|
||||||
|
then
|
||||||
|
echo "Stopping NetBird daemon"
|
||||||
|
osascript -e 'quit app "Netbird UI"' 2> /dev/null || true
|
||||||
|
netbird service stop 2> /dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# start netbird daemon service
|
# start netbird daemon service
|
||||||
echo "Starting Netbird daemon"
|
echo "Starting Netbird daemon"
|
||||||
netbird service install || true
|
netbird service install 2> /dev/null || true
|
||||||
netbird service start || true
|
netbird service start || true
|
||||||
|
|
||||||
|
# start app
|
||||||
|
open /Applications/Netbird\ UI.app
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
APP=/Applications/NetBird.app
|
APP=/Applications/NetBird.app
|
||||||
AGENT=/usr/local/bin/netbird
|
AGENT=/usr/local/bin/netbird
|
||||||
LOG_FILE=/var/log/netbird/client_install.log
|
LOG_FILE=/var/log/netbird/client_post_install.log
|
||||||
|
|
||||||
mkdir -p /var/log/netbird/
|
mkdir -p /var/log/netbird/
|
||||||
mkdir -p /usr/local/bin/
|
mkdir -p /usr/local/bin/
|
||||||
@@ -17,7 +19,7 @@ mkdir -p /usr/local/bin/
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ln -s $APP/Contents/MacOS/netbird $AGENT
|
ln -fs $APP/Contents/MacOS/netbird $AGENT
|
||||||
if test -f $AGENT; then
|
if test -f $AGENT; then
|
||||||
echo "NetBird binary linked successfully."
|
echo "NetBird binary linked successfully."
|
||||||
else
|
else
|
||||||
@@ -25,11 +27,12 @@ mkdir -p /usr/local/bin/
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$AGENT service install
|
$AGENT service install || true
|
||||||
$AGENT service start
|
$AGENT service start || true
|
||||||
|
|
||||||
open $APP
|
open $APP
|
||||||
|
|
||||||
echo "Finished Netbird installation successfully"
|
echo "Finished Netbird installation successfully"
|
||||||
exit 0 # all good
|
exit 0 # all good
|
||||||
} &> $LOG_FILE
|
} &> $LOG_FILE
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
LOG_FILE=/var/log/netbird/client_install.log
|
set -x
|
||||||
|
|
||||||
|
LOG_FILE=/var/log/netbird/client_pre_install.log
|
||||||
|
AGENT=/usr/local/bin/netbird
|
||||||
|
|
||||||
mkdir -p /var/log/netbird/
|
mkdir -p /var/log/netbird/
|
||||||
|
|
||||||
{
|
{
|
||||||
|
osascript -e 'quit app "Netbird"' || true
|
||||||
|
$AGENT service stop || true
|
||||||
|
|
||||||
echo "Preinstall complete"
|
echo "Preinstall complete"
|
||||||
exit 0 # all good
|
exit 0 # all good
|
||||||
} &> $LOG_FILE
|
} &> $LOG_FILE
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import (
|
|||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
|
appKeepAlive "github.com/netbirdio/netbird/keepalive"
|
||||||
"github.com/netbirdio/netbird/signal/proto"
|
"github.com/netbirdio/netbird/signal/proto"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultSendTimeout = 5 * time.Second
|
const defaultSendTimeout = 5 * time.Second
|
||||||
@@ -73,6 +75,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
|
|
||||||
sigCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
sigCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
sigCtx,
|
sigCtx,
|
||||||
addr,
|
addr,
|
||||||
@@ -87,9 +90,14 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
log.Errorf("failed to connect to the signalling server %v", err)
|
log.Errorf("failed to connect to the signalling server %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("connected to Signal Service: %v", conn.Target())
|
log.Debugf("connected to Signal Service: %v", conn.Target())
|
||||||
|
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
proto.HeaderId: key.PublicKey().String(), // add key fingerprint to the request header to be identified on the server side
|
||||||
|
appKeepAlive.GrpcVersionHeaderKey: version.NetbirdVersion(), // add version info to ensure keep alive is supported
|
||||||
|
})
|
||||||
|
ctx = metadata.NewOutgoingContext(ctx, md)
|
||||||
|
|
||||||
return &GrpcClient{
|
return &GrpcClient{
|
||||||
realClient: proto.NewSignalExchangeClient(conn),
|
realClient: proto.NewSignalExchangeClient(conn),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -146,7 +154,7 @@ func (c *GrpcClient) Receive(msgHandler func(msg *proto.Message) error) error {
|
|||||||
// todo once the key rotation logic has been implemented, consider changing to some other identifier (received from management)
|
// todo once the key rotation logic has been implemented, consider changing to some other identifier (received from management)
|
||||||
ctx, cancelStream := context.WithCancel(c.ctx)
|
ctx, cancelStream := context.WithCancel(c.ctx)
|
||||||
defer cancelStream()
|
defer cancelStream()
|
||||||
stream, err := c.connect(ctx, c.key.PublicKey().String())
|
stream, err := c.connect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
|
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
|
||||||
return err
|
return err
|
||||||
@@ -208,13 +216,9 @@ func (c *GrpcClient) getStreamStatusChan() <-chan struct{} {
|
|||||||
return c.connectedCh
|
return c.connectedCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GrpcClient) connect(ctx context.Context, key string) (proto.SignalExchange_ConnectStreamClient, error) {
|
func (c *GrpcClient) connect(ctx context.Context) (proto.SignalExchange_ConnectStreamClient, error) {
|
||||||
c.stream = nil
|
c.stream = nil
|
||||||
|
stream, err := c.realClient.ConnectStream(ctx, grpc.WaitForReady(true))
|
||||||
// add key fingerprint to the request header to be identified on the server side
|
|
||||||
md := metadata.New(map[string]string{proto.HeaderId: key})
|
|
||||||
metaCtx := metadata.NewOutgoingContext(ctx, md)
|
|
||||||
stream, err := c.realClient.ConnectStream(metaCtx, grpc.WaitForReady(true))
|
|
||||||
c.stream = stream
|
c.stream = stream
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -366,6 +370,12 @@ func (c *GrpcClient) receive(stream proto.SignalExchange_ConnectStreamClient,
|
|||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if appKeepAlive.IsKeepAliveMsg(msg.Body) {
|
||||||
|
log.Tracef("received keepalive")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
log.Tracef("received a new message from Peer [fingerprint: %s]", msg.Key)
|
log.Tracef("received a new message from Peer [fingerprint: %s]", msg.Key)
|
||||||
|
|
||||||
decryptedMessage, err := c.decryptMessage(msg)
|
decryptedMessage, err := c.decryptMessage(msg)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
@@ -14,15 +13,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
|
||||||
"github.com/netbirdio/netbird/signal/proto"
|
|
||||||
"github.com/netbirdio/netbird/signal/server"
|
|
||||||
"github.com/netbirdio/netbird/util"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/encryption"
|
||||||
|
appKeepAlive "github.com/netbirdio/netbird/keepalive"
|
||||||
|
"github.com/netbirdio/netbird/signal/proto"
|
||||||
|
"github.com/netbirdio/netbird/signal/server"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -93,6 +95,13 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, signalKaep, signalKasp)
|
opts = append(opts, signalKaep, signalKasp)
|
||||||
|
|
||||||
|
ka := appKeepAlive.NewKeepAlive(&proto.KeepAlive{})
|
||||||
|
defer ka.Stop()
|
||||||
|
sInterc := grpc.StreamInterceptor(ka.StreamInterceptor())
|
||||||
|
uInterc := grpc.UnaryInterceptor(ka.UnaryInterceptor())
|
||||||
|
opts = append(opts, sInterc, uInterc)
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(opts...)
|
grpcServer := grpc.NewServer(opts...)
|
||||||
proto.RegisterSignalExchangeServer(grpcServer, server.NewServer())
|
proto.RegisterSignalExchangeServer(grpcServer, server.NewServer())
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.9
|
// protoc v3.21.12
|
||||||
// source: signalexchange.proto
|
// source: signalexchange.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -347,6 +347,44 @@ func (x *Mode) GetDirect() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeepAlive struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KeepAlive) Reset() {
|
||||||
|
*x = KeepAlive{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_signalexchange_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KeepAlive) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*KeepAlive) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *KeepAlive) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_signalexchange_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use KeepAlive.ProtoReflect.Descriptor instead.
|
||||||
|
func (*KeepAlive) Descriptor() ([]byte, []int) {
|
||||||
|
return file_signalexchange_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
var File_signalexchange_proto protoreflect.FileDescriptor
|
var File_signalexchange_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_signalexchange_proto_rawDesc = []byte{
|
var file_signalexchange_proto_rawDesc = []byte{
|
||||||
@@ -388,20 +426,20 @@ var file_signalexchange_proto_rawDesc = []byte{
|
|||||||
0x45, 0x10, 0x04, 0x22, 0x2e, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64,
|
0x45, 0x10, 0x04, 0x22, 0x2e, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64,
|
||||||
0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64,
|
0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64,
|
||||||
0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72,
|
0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72,
|
||||||
0x65, 0x63, 0x74, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78,
|
0x65, 0x63, 0x74, 0x22, 0x0b, 0x0a, 0x09, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,
|
||||||
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20,
|
0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, 0x63, 0x68, 0x61,
|
||||||
0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e,
|
0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, 0x69,
|
||||||
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63,
|
||||||
0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67,
|
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e,
|
||||||
0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45,
|
||||||
0x67, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53,
|
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
||||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78,
|
0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x65,
|
||||||
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61,
|
||||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,
|
0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63,
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42,
|
0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
||||||
0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06,
|
||||||
0x33,
|
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -417,13 +455,14 @@ func file_signalexchange_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_signalexchange_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_signalexchange_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_signalexchange_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
var file_signalexchange_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_signalexchange_proto_goTypes = []interface{}{
|
var file_signalexchange_proto_goTypes = []interface{}{
|
||||||
(Body_Type)(0), // 0: signalexchange.Body.Type
|
(Body_Type)(0), // 0: signalexchange.Body.Type
|
||||||
(*EncryptedMessage)(nil), // 1: signalexchange.EncryptedMessage
|
(*EncryptedMessage)(nil), // 1: signalexchange.EncryptedMessage
|
||||||
(*Message)(nil), // 2: signalexchange.Message
|
(*Message)(nil), // 2: signalexchange.Message
|
||||||
(*Body)(nil), // 3: signalexchange.Body
|
(*Body)(nil), // 3: signalexchange.Body
|
||||||
(*Mode)(nil), // 4: signalexchange.Mode
|
(*Mode)(nil), // 4: signalexchange.Mode
|
||||||
|
(*KeepAlive)(nil), // 5: signalexchange.KeepAlive
|
||||||
}
|
}
|
||||||
var file_signalexchange_proto_depIdxs = []int32{
|
var file_signalexchange_proto_depIdxs = []int32{
|
||||||
3, // 0: signalexchange.Message.body:type_name -> signalexchange.Body
|
3, // 0: signalexchange.Message.body:type_name -> signalexchange.Body
|
||||||
@@ -494,6 +533,18 @@ func file_signalexchange_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_signalexchange_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*KeepAlive); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
file_signalexchange_proto_msgTypes[3].OneofWrappers = []interface{}{}
|
file_signalexchange_proto_msgTypes[3].OneofWrappers = []interface{}{}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
@@ -502,7 +553,7 @@ func file_signalexchange_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_signalexchange_proto_rawDesc,
|
RawDescriptor: file_signalexchange_proto_rawDesc,
|
||||||
NumEnums: 1,
|
NumEnums: 1,
|
||||||
NumMessages: 4,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,3 +63,5 @@ message Body {
|
|||||||
message Mode {
|
message Mode {
|
||||||
optional bool direct = 1;
|
optional bool direct = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message KeepAlive {}
|
||||||
Reference in New Issue
Block a user