mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-20 01:06:45 +00:00
Compare commits
51 Commits
deploy/pos
...
synology-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cf1644bc4 | ||
|
|
5a1f8f13a2 | ||
|
|
e71059d245 | ||
|
|
91fa2e20a0 | ||
|
|
61034aaf4d | ||
|
|
b8717b8956 | ||
|
|
50201d63c2 | ||
|
|
d11b39282b | ||
|
|
bd58eea8ea | ||
|
|
a5811a2d7d | ||
|
|
a680f80ed9 | ||
|
|
10fbdc2c4a | ||
|
|
1444fbe104 | ||
|
|
650bca7ca8 | ||
|
|
570e28d227 | ||
|
|
272ade07a8 | ||
|
|
263abe4862 | ||
|
|
ceee421a05 | ||
|
|
0a75da6fb7 | ||
|
|
920877964f | ||
|
|
2e0047daea | ||
|
|
ce0718fcb5 | ||
|
|
c590518e0c | ||
|
|
f309b120cd | ||
|
|
7357a9954c | ||
|
|
13b63eebc1 | ||
|
|
735ed7ab34 | ||
|
|
961d9198ef | ||
|
|
df4ca01848 | ||
|
|
4e7c17756c | ||
|
|
6a4935139d | ||
|
|
35dd991776 | ||
|
|
3598418206 | ||
|
|
e435e39158 | ||
|
|
fd26e989e3 | ||
|
|
4424162bce | ||
|
|
54b045d9ca | ||
|
|
71c6437bab | ||
|
|
7b254cb966 | ||
|
|
8f3a0f2c38 | ||
|
|
1f33e2e003 | ||
|
|
1e6addaa65 | ||
|
|
f51dc13f8c | ||
|
|
3477108ce7 | ||
|
|
012e624296 | ||
|
|
4c5e987e02 | ||
|
|
a80c8b0176 | ||
|
|
9e01155d2e | ||
|
|
3c3111ad01 | ||
|
|
b74078fd95 | ||
|
|
77488ad11a |
2
.github/workflows/golang-test-linux.yml
vendored
2
.github/workflows/golang-test-linux.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: [ '386','amd64' ]
|
arch: [ '386','amd64' ]
|
||||||
store: [ 'jsonfile', 'sqlite' ]
|
store: [ 'jsonfile', 'sqlite', 'postgres']
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
|
|||||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: codespell
|
- name: codespell
|
||||||
uses: codespell-project/actions-codespell@v2
|
uses: codespell-project/actions-codespell@v2
|
||||||
with:
|
with:
|
||||||
ignore_words_list: erro,clienta
|
ignore_words_list: erro,clienta,hastable,
|
||||||
skip: go.mod,go.sum
|
skip: go.mod,go.sum
|
||||||
only_warn: 1
|
only_warn: 1
|
||||||
golangci:
|
golangci:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
- name: Setup NDK
|
- name: Setup NDK
|
||||||
run: /usr/local/lib/android/sdk/cmdline-tools/7.0/bin/sdkmanager --install "ndk;23.1.7779620"
|
run: /usr/local/lib/android/sdk/cmdline-tools/7.0/bin/sdkmanager --install "ndk;23.1.7779620"
|
||||||
- name: install gomobile
|
- name: install gomobile
|
||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed
|
||||||
- name: gomobile init
|
- name: gomobile init
|
||||||
run: gomobile init
|
run: gomobile init
|
||||||
- name: build android netbird lib
|
- name: build android netbird lib
|
||||||
@@ -56,10 +56,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: "1.21.x"
|
go-version: "1.21.x"
|
||||||
- name: install gomobile
|
- name: install gomobile
|
||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed
|
||||||
- name: gomobile init
|
- name: gomobile init
|
||||||
run: gomobile init
|
run: gomobile init
|
||||||
- name: build iOS netbird lib
|
- name: build iOS netbird lib
|
||||||
run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o $GITHUB_WORKSPACE/NetBirdSDK.xcframework $GITHUB_WORKSPACE/client/ios/NetBirdSDK
|
run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o ./NetBirdSDK.xcframework ./client/ios/NetBirdSDK
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -7,17 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- 'go.mod'
|
|
||||||
- 'go.sum'
|
|
||||||
- '.goreleaser.yml'
|
|
||||||
- '.goreleaser_ui.yaml'
|
|
||||||
- '.goreleaser_ui_darwin.yaml'
|
|
||||||
- '.github/workflows/release.yml'
|
|
||||||
- 'release_files/**'
|
|
||||||
- '**/Dockerfile'
|
|
||||||
- '**/Dockerfile.*'
|
|
||||||
- 'client/ui/**'
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SIGN_PIPE_VER: "v0.0.11"
|
SIGN_PIPE_VER: "v0.0.11"
|
||||||
@@ -106,6 +96,27 @@ jobs:
|
|||||||
name: release
|
name: release
|
||||||
path: dist/
|
path: dist/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
-
|
||||||
|
name: upload linux packages
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: linux-packages
|
||||||
|
path: dist/netbird_linux**
|
||||||
|
retention-days: 3
|
||||||
|
-
|
||||||
|
name: upload windows packages
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: windows-packages
|
||||||
|
path: dist/netbird_windows**
|
||||||
|
retention-days: 3
|
||||||
|
-
|
||||||
|
name: upload macos packages
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: macos-packages
|
||||||
|
path: dist/netbird_darwin**
|
||||||
|
retention-days: 3
|
||||||
|
|
||||||
release_ui:
|
release_ui:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
|
|
||||||
### Open-Source Network Security in a Single Platform
|
### Open-Source Network Security in a Single Platform
|
||||||
|
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
### Key features
|
### Key features
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM alpine:3.18.5
|
FROM alpine:3.18.5
|
||||||
RUN apk add --no-cache ca-certificates iptables ip6tables
|
RUN apk add --no-cache ca-certificates iptables ip6tables
|
||||||
ENV NB_FOREGROUND_MODE=true
|
ENV NB_FOREGROUND_MODE=true
|
||||||
ENTRYPOINT [ "/go/bin/netbird","up"]
|
ENTRYPOINT [ "/usr/local/bin/netbird","up"]
|
||||||
COPY netbird /go/bin/netbird
|
COPY netbird /usr/local/bin/netbird
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build android
|
||||||
|
|
||||||
package android
|
package android
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/formatter"
|
"github.com/netbirdio/netbird/formatter"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnectionListener export internal Listener for mobile
|
// ConnectionListener export internal Listener for mobile
|
||||||
@@ -59,6 +62,7 @@ type Client struct {
|
|||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
func NewClient(cfgFile, deviceName string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
|
func NewClient(cfgFile, deviceName string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
|
||||||
|
net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket)
|
||||||
return &Client{
|
return &Client{
|
||||||
cfgFile: cfgFile,
|
cfgFile: cfgFile,
|
||||||
deviceName: deviceName,
|
deviceName: deviceName,
|
||||||
@@ -97,7 +101,8 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
|
|||||||
|
|
||||||
// todo do not throw error in case of cancelled context
|
// todo do not throw error in case of cancelled context
|
||||||
ctx = internal.CtxInitState(ctx)
|
ctx = internal.CtxInitState(ctx)
|
||||||
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
|
connectClient := internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
|
return connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
|
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
|
||||||
@@ -122,7 +127,8 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener
|
|||||||
|
|
||||||
// todo do not throw error in case of cancelled context
|
// todo do not throw error in case of cancelled context
|
||||||
ctx = internal.CtxInitState(ctx)
|
ctx = internal.CtxInitState(ctx)
|
||||||
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
|
connectClient := internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
|
return connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the internal client and free the resources
|
// Stop the internal client and free the resources
|
||||||
|
|||||||
212
client/anonymize/anonymize.go
Normal file
212
client/anonymize/anonymize.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
package anonymize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Anonymizer struct {
|
||||||
|
ipAnonymizer map[netip.Addr]netip.Addr
|
||||||
|
domainAnonymizer map[string]string
|
||||||
|
currentAnonIPv4 netip.Addr
|
||||||
|
currentAnonIPv6 netip.Addr
|
||||||
|
startAnonIPv4 netip.Addr
|
||||||
|
startAnonIPv6 netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultAddresses() (netip.Addr, netip.Addr) {
|
||||||
|
// 192.51.100.0, 100::
|
||||||
|
return netip.AddrFrom4([4]byte{198, 51, 100, 0}), netip.AddrFrom16([16]byte{0x01})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnonymizer(startIPv4, startIPv6 netip.Addr) *Anonymizer {
|
||||||
|
return &Anonymizer{
|
||||||
|
ipAnonymizer: map[netip.Addr]netip.Addr{},
|
||||||
|
domainAnonymizer: map[string]string{},
|
||||||
|
currentAnonIPv4: startIPv4,
|
||||||
|
currentAnonIPv6: startIPv6,
|
||||||
|
startAnonIPv4: startIPv4,
|
||||||
|
startAnonIPv6: startIPv6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Anonymizer) AnonymizeIP(ip netip.Addr) netip.Addr {
|
||||||
|
if ip.IsLoopback() ||
|
||||||
|
ip.IsLinkLocalUnicast() ||
|
||||||
|
ip.IsLinkLocalMulticast() ||
|
||||||
|
ip.IsInterfaceLocalMulticast() ||
|
||||||
|
ip.IsPrivate() ||
|
||||||
|
ip.IsUnspecified() ||
|
||||||
|
ip.IsMulticast() ||
|
||||||
|
isWellKnown(ip) ||
|
||||||
|
a.isInAnonymizedRange(ip) {
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := a.ipAnonymizer[ip]; !ok {
|
||||||
|
if ip.Is4() {
|
||||||
|
a.ipAnonymizer[ip] = a.currentAnonIPv4
|
||||||
|
a.currentAnonIPv4 = a.currentAnonIPv4.Next()
|
||||||
|
} else {
|
||||||
|
a.ipAnonymizer[ip] = a.currentAnonIPv6
|
||||||
|
a.currentAnonIPv6 = a.currentAnonIPv6.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.ipAnonymizer[ip]
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInAnonymizedRange checks if an IP is within the range of already assigned anonymized IPs
|
||||||
|
func (a *Anonymizer) isInAnonymizedRange(ip netip.Addr) bool {
|
||||||
|
if ip.Is4() && ip.Compare(a.startAnonIPv4) >= 0 && ip.Compare(a.currentAnonIPv4) <= 0 {
|
||||||
|
return true
|
||||||
|
} else if !ip.Is4() && ip.Compare(a.startAnonIPv6) >= 0 && ip.Compare(a.currentAnonIPv6) <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Anonymizer) AnonymizeIPString(ip string) string {
|
||||||
|
addr, err := netip.ParseAddr(ip)
|
||||||
|
if err != nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.AnonymizeIP(addr).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Anonymizer) AnonymizeDomain(domain string) string {
|
||||||
|
if strings.HasSuffix(domain, "netbird.io") ||
|
||||||
|
strings.HasSuffix(domain, "netbird.selfhosted") ||
|
||||||
|
strings.HasSuffix(domain, "netbird.cloud") ||
|
||||||
|
strings.HasSuffix(domain, "netbird.stage") ||
|
||||||
|
strings.HasSuffix(domain, ".domain") {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(domain, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDomain := parts[len(parts)-2] + "." + parts[len(parts)-1]
|
||||||
|
|
||||||
|
anonymized, ok := a.domainAnonymizer[baseDomain]
|
||||||
|
if !ok {
|
||||||
|
anonymizedBase := "anon-" + generateRandomString(5) + ".domain"
|
||||||
|
a.domainAnonymizer[baseDomain] = anonymizedBase
|
||||||
|
anonymized = anonymizedBase
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(domain, baseDomain, anonymized, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Anonymizer) AnonymizeURI(uri string) string {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
var anonymizedHost string
|
||||||
|
if u.Opaque != "" {
|
||||||
|
host, port, err := net.SplitHostPort(u.Opaque)
|
||||||
|
if err == nil {
|
||||||
|
anonymizedHost = fmt.Sprintf("%s:%s", a.AnonymizeDomain(host), port)
|
||||||
|
} else {
|
||||||
|
anonymizedHost = a.AnonymizeDomain(u.Opaque)
|
||||||
|
}
|
||||||
|
u.Opaque = anonymizedHost
|
||||||
|
} else if u.Host != "" {
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err == nil {
|
||||||
|
anonymizedHost = fmt.Sprintf("%s:%s", a.AnonymizeDomain(host), port)
|
||||||
|
} else {
|
||||||
|
anonymizedHost = a.AnonymizeDomain(u.Host)
|
||||||
|
}
|
||||||
|
u.Host = anonymizedHost
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Anonymizer) AnonymizeString(str string) string {
|
||||||
|
ipv4Regex := regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`)
|
||||||
|
ipv6Regex := regexp.MustCompile(`\b([0-9a-fA-F:]+:+[0-9a-fA-F]{0,4})(?:%[0-9a-zA-Z]+)?(?:\/[0-9]{1,3})?(?::[0-9]{1,5})?\b`)
|
||||||
|
|
||||||
|
str = ipv4Regex.ReplaceAllStringFunc(str, a.AnonymizeIPString)
|
||||||
|
str = ipv6Regex.ReplaceAllStringFunc(str, a.AnonymizeIPString)
|
||||||
|
|
||||||
|
for domain, anonDomain := range a.domainAnonymizer {
|
||||||
|
str = strings.ReplaceAll(str, domain, anonDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
str = a.AnonymizeSchemeURI(str)
|
||||||
|
str = a.AnonymizeDNSLogLine(str)
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnonymizeSchemeURI finds and anonymizes URIs with stun, stuns, turn, and turns schemes.
|
||||||
|
func (a *Anonymizer) AnonymizeSchemeURI(text string) string {
|
||||||
|
re := regexp.MustCompile(`(?i)\b(stuns?:|turns?:|https?://)\S+\b`)
|
||||||
|
|
||||||
|
return re.ReplaceAllStringFunc(text, a.AnonymizeURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnonymizeDNSLogLine anonymizes domain names in DNS log entries by replacing them with a random string.
|
||||||
|
func (a *Anonymizer) AnonymizeDNSLogLine(logEntry string) string {
|
||||||
|
domainPattern := `dns\.Question{Name:"([^"]+)",`
|
||||||
|
domainRegex := regexp.MustCompile(domainPattern)
|
||||||
|
|
||||||
|
return domainRegex.ReplaceAllStringFunc(logEntry, func(match string) string {
|
||||||
|
parts := strings.Split(match, `"`)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
domain := parts[1]
|
||||||
|
if strings.HasSuffix(domain, ".domain") {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
randomDomain := generateRandomString(10) + ".domain"
|
||||||
|
return strings.Replace(match, domain, randomDomain, 1)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWellKnown(addr netip.Addr) bool {
|
||||||
|
wellKnown := []string{
|
||||||
|
"8.8.8.8", "8.8.4.4", // Google DNS IPv4
|
||||||
|
"2001:4860:4860::8888", "2001:4860:4860::8844", // Google DNS IPv6
|
||||||
|
"1.1.1.1", "1.0.0.1", // Cloudflare DNS IPv4
|
||||||
|
"2606:4700:4700::1111", "2606:4700:4700::1001", // Cloudflare DNS IPv6
|
||||||
|
"9.9.9.9", "149.112.112.112", // Quad9 DNS IPv4
|
||||||
|
"2620:fe::fe", "2620:fe::9", // Quad9 DNS IPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(wellKnown, addr.String()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cgnatRangeStart := netip.AddrFrom4([4]byte{100, 64, 0, 0})
|
||||||
|
cgnatRange := netip.PrefixFrom(cgnatRangeStart, 10)
|
||||||
|
|
||||||
|
return cgnatRange.Contains(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomString(length int) string {
|
||||||
|
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
result := make([]byte, length)
|
||||||
|
for i := range result {
|
||||||
|
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[i] = letters[num.Int64()]
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
223
client/anonymize/anonymize_test.go
Normal file
223
client/anonymize/anonymize_test.go
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
package anonymize_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/anonymize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAnonymizeIP(t *testing.T) {
|
||||||
|
startIPv4 := netip.MustParseAddr("198.51.100.0")
|
||||||
|
startIPv6 := netip.MustParseAddr("100::")
|
||||||
|
anonymizer := anonymize.NewAnonymizer(startIPv4, startIPv6)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ip string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{"Well known", "8.8.8.8", "8.8.8.8"},
|
||||||
|
{"First Public IPv4", "1.2.3.4", "198.51.100.0"},
|
||||||
|
{"Second Public IPv4", "4.3.2.1", "198.51.100.1"},
|
||||||
|
{"Repeated IPv4", "1.2.3.4", "198.51.100.0"},
|
||||||
|
{"Private IPv4", "192.168.1.1", "192.168.1.1"},
|
||||||
|
{"First Public IPv6", "2607:f8b0:4005:805::200e", "100::"},
|
||||||
|
{"Second Public IPv6", "a::b", "100::1"},
|
||||||
|
{"Repeated IPv6", "2607:f8b0:4005:805::200e", "100::"},
|
||||||
|
{"Private IPv6", "fe80::1", "fe80::1"},
|
||||||
|
{"In Range IPv4", "198.51.100.2", "198.51.100.2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ip := netip.MustParseAddr(tc.ip)
|
||||||
|
anonymizedIP := anonymizer.AnonymizeIP(ip)
|
||||||
|
if anonymizedIP.String() != tc.expect {
|
||||||
|
t.Errorf("%s: expected %s, got %s", tc.name, tc.expect, anonymizedIP)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizeDNSLogLine(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{})
|
||||||
|
testLog := `2024-04-23T20:01:11+02:00 TRAC client/internal/dns/local.go:25: received question: dns.Question{Name:"example.com", Qtype:0x1c, Qclass:0x1}`
|
||||||
|
|
||||||
|
result := anonymizer.AnonymizeDNSLogLine(testLog)
|
||||||
|
require.NotEqual(t, testLog, result)
|
||||||
|
assert.NotContains(t, result, "example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizeDomain(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
domain string
|
||||||
|
expectPattern string
|
||||||
|
shouldAnonymize bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"General Domain",
|
||||||
|
"example.com",
|
||||||
|
`^anon-[a-zA-Z0-9]+\.domain$`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Subdomain",
|
||||||
|
"sub.example.com",
|
||||||
|
`^sub\.anon-[a-zA-Z0-9]+\.domain$`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Protected Domain",
|
||||||
|
"netbird.io",
|
||||||
|
`^netbird\.io$`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := anonymizer.AnonymizeDomain(tc.domain)
|
||||||
|
if tc.shouldAnonymize {
|
||||||
|
assert.Regexp(t, tc.expectPattern, result, "The anonymized domain should match the expected pattern")
|
||||||
|
assert.NotContains(t, result, tc.domain, "The original domain should not be present in the result")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tc.domain, result, "Protected domains should not be anonymized")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizeURI(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uri string
|
||||||
|
regex string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"HTTP URI with Port",
|
||||||
|
"http://example.com:80/path",
|
||||||
|
`^http://anon-[a-zA-Z0-9]+\.domain:80/path$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HTTP URI without Port",
|
||||||
|
"http://example.com/path",
|
||||||
|
`^http://anon-[a-zA-Z0-9]+\.domain/path$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Opaque URI with Port",
|
||||||
|
"stun:example.com:80?transport=udp",
|
||||||
|
`^stun:anon-[a-zA-Z0-9]+\.domain:80\?transport=udp$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Opaque URI without Port",
|
||||||
|
"stun:example.com?transport=udp",
|
||||||
|
`^stun:anon-[a-zA-Z0-9]+\.domain\?transport=udp$`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := anonymizer.AnonymizeURI(tc.uri)
|
||||||
|
assert.Regexp(t, regexp.MustCompile(tc.regex), result, "URI should match expected pattern")
|
||||||
|
require.NotContains(t, result, "example.com", "Original domain should not be present")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizeSchemeURI(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{"STUN URI in text", "Connection made via stun:example.com", `Connection made via stun:anon-[a-zA-Z0-9]+\.domain`},
|
||||||
|
{"TURN URI in log", "Failed attempt turn:some.example.com:3478?transport=tcp: retrying", `Failed attempt turn:some.anon-[a-zA-Z0-9]+\.domain:3478\?transport=tcp: retrying`},
|
||||||
|
{"HTTPS URI in message", "Visit https://example.com for more", `Visit https://anon-[a-zA-Z0-9]+\.domain for more`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := anonymizer.AnonymizeSchemeURI(tc.input)
|
||||||
|
assert.Regexp(t, tc.expect, result, "The anonymized output should match expected pattern")
|
||||||
|
require.NotContains(t, result, "example.com", "Original domain should not be present")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizString_MemorizedDomain(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{})
|
||||||
|
domain := "example.com"
|
||||||
|
anonymizedDomain := anonymizer.AnonymizeDomain(domain)
|
||||||
|
|
||||||
|
sampleString := "This is a test string including the domain example.com which should be anonymized."
|
||||||
|
|
||||||
|
firstPassResult := anonymizer.AnonymizeString(sampleString)
|
||||||
|
secondPassResult := anonymizer.AnonymizeString(firstPassResult)
|
||||||
|
|
||||||
|
assert.Contains(t, firstPassResult, anonymizedDomain, "The domain should be anonymized in the first pass")
|
||||||
|
assert.NotContains(t, firstPassResult, domain, "The original domain should not appear in the first pass output")
|
||||||
|
|
||||||
|
assert.Equal(t, firstPassResult, secondPassResult, "The second pass should not further anonymize the string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizeString_DoubleURI(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{})
|
||||||
|
domain := "example.com"
|
||||||
|
anonymizedDomain := anonymizer.AnonymizeDomain(domain)
|
||||||
|
|
||||||
|
sampleString := "Check out our site at https://example.com for more info."
|
||||||
|
|
||||||
|
firstPassResult := anonymizer.AnonymizeString(sampleString)
|
||||||
|
secondPassResult := anonymizer.AnonymizeString(firstPassResult)
|
||||||
|
|
||||||
|
assert.Contains(t, firstPassResult, "https://"+anonymizedDomain, "The URI should be anonymized in the first pass")
|
||||||
|
assert.NotContains(t, firstPassResult, "https://example.com", "The original URI should not appear in the first pass output")
|
||||||
|
|
||||||
|
assert.Equal(t, firstPassResult, secondPassResult, "The second pass should not further anonymize the URI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymizeString_IPAddresses(t *testing.T) {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "IPv4 Address",
|
||||||
|
input: "Error occurred at IP 122.138.1.1",
|
||||||
|
expect: "Error occurred at IP 198.51.100.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 Address",
|
||||||
|
input: "Access attempted from 2001:db8::ff00:42",
|
||||||
|
expect: "Access attempted from 100::",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 Address with Port",
|
||||||
|
input: "Access attempted from [2001:db8::ff00:42]:8080",
|
||||||
|
expect: "Access attempted from [100::]:8080",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Both IPv4 and IPv6",
|
||||||
|
input: "IPv4: 142.108.0.1 and IPv6: 2001:db8::ff00:43",
|
||||||
|
expect: "IPv4: 198.51.100.1 and IPv6: 100::1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := anonymizer.AnonymizeString(tc.input)
|
||||||
|
assert.Equal(t, tc.expect, result, "IP addresses should be anonymized correctly")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
248
client/cmd/debug.go
Normal file
248
client/cmd/debug.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var debugCmd = &cobra.Command{
|
||||||
|
Use: "debug",
|
||||||
|
Short: "Debugging commands",
|
||||||
|
Long: "Provides commands for debugging and logging control within the Netbird daemon.",
|
||||||
|
}
|
||||||
|
|
||||||
|
var debugBundleCmd = &cobra.Command{
|
||||||
|
Use: "bundle",
|
||||||
|
Example: " netbird debug bundle",
|
||||||
|
Short: "Create a debug bundle",
|
||||||
|
Long: "Generates a compressed archive of the daemon's logs and status for debugging purposes.",
|
||||||
|
RunE: debugBundle,
|
||||||
|
}
|
||||||
|
|
||||||
|
var logCmd = &cobra.Command{
|
||||||
|
Use: "log",
|
||||||
|
Short: "Manage logging for the Netbird daemon",
|
||||||
|
Long: `Commands to manage logging settings for the Netbird daemon, including ICE, gRPC, and general log levels.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var logLevelCmd = &cobra.Command{
|
||||||
|
Use: "level <level>",
|
||||||
|
Short: "Set the logging level for this session",
|
||||||
|
Long: `Sets the logging level for the current session. This setting is temporary and will revert to the default on daemon restart.
|
||||||
|
Available log levels are:
|
||||||
|
panic: for panic level, highest level of severity
|
||||||
|
fatal: for fatal level errors that cause the program to exit
|
||||||
|
error: for error conditions
|
||||||
|
warn: for warning conditions
|
||||||
|
info: for informational messages
|
||||||
|
debug: for debug-level messages
|
||||||
|
trace: for trace-level messages, which include more fine-grained information than debug`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: setLogLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
var forCmd = &cobra.Command{
|
||||||
|
Use: "for <time>",
|
||||||
|
Short: "Run debug logs for a specified duration and create a debug bundle",
|
||||||
|
Long: `Sets the logging level to trace, runs for the specified duration, and then generates a debug bundle.`,
|
||||||
|
Example: " netbird debug for 5m",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runForDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugBundle(cmd *cobra.Command, _ []string) error {
|
||||||
|
conn, err := getClient(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{
|
||||||
|
Anonymize: anonymizeFlag,
|
||||||
|
Status: getStatusOutput(cmd),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println(resp.GetPath())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLogLevel(cmd *cobra.Command, args []string) error {
|
||||||
|
conn, err := getClient(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
level := parseLogLevel(args[0])
|
||||||
|
if level == proto.LogLevel_UNKNOWN {
|
||||||
|
return fmt.Errorf("unknown log level: %s. Available levels are: panic, fatal, error, warn, info, debug, trace\n", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{
|
||||||
|
Level: level,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set log level: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println("Log level set successfully to", args[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLogLevel(level string) proto.LogLevel {
|
||||||
|
switch strings.ToLower(level) {
|
||||||
|
case "panic":
|
||||||
|
return proto.LogLevel_PANIC
|
||||||
|
case "fatal":
|
||||||
|
return proto.LogLevel_FATAL
|
||||||
|
case "error":
|
||||||
|
return proto.LogLevel_ERROR
|
||||||
|
case "warn":
|
||||||
|
return proto.LogLevel_WARN
|
||||||
|
case "info":
|
||||||
|
return proto.LogLevel_INFO
|
||||||
|
case "debug":
|
||||||
|
return proto.LogLevel_DEBUG
|
||||||
|
case "trace":
|
||||||
|
return proto.LogLevel_TRACE
|
||||||
|
default:
|
||||||
|
return proto.LogLevel_UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runForDuration(cmd *cobra.Command, args []string) error {
|
||||||
|
duration, err := time.ParseDuration(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid duration format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := getClient(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
|
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
cmd.Println("Netbird down")
|
||||||
|
|
||||||
|
_, err = client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{
|
||||||
|
Level: proto.LogLevel_TRACE,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set log level to trace: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
cmd.Println("Log level set to trace.")
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to up: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
cmd.Println("Netbird up")
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
headerPostUp := fmt.Sprintf("----- Netbird post-up - Timestamp: %s", time.Now().Format(time.RFC3339))
|
||||||
|
statusOutput := fmt.Sprintf("%s\n%s", headerPostUp, getStatusOutput(cmd))
|
||||||
|
|
||||||
|
if waitErr := waitForDurationOrCancel(cmd.Context(), duration, cmd); waitErr != nil {
|
||||||
|
return waitErr
|
||||||
|
}
|
||||||
|
cmd.Println("\nDuration completed")
|
||||||
|
|
||||||
|
headerPreDown := fmt.Sprintf("----- Netbird pre-down - Timestamp: %s - Duration: %s", time.Now().Format(time.RFC3339), duration)
|
||||||
|
statusOutput = fmt.Sprintf("%s\n%s\n%s", statusOutput, headerPreDown, getStatusOutput(cmd))
|
||||||
|
|
||||||
|
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
cmd.Println("Netbird down")
|
||||||
|
|
||||||
|
// TODO reset log level
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
cmd.Println("Creating debug bundle...")
|
||||||
|
|
||||||
|
resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{
|
||||||
|
Anonymize: anonymizeFlag,
|
||||||
|
Status: statusOutput,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println(resp.GetPath())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStatusOutput(cmd *cobra.Command) string {
|
||||||
|
var statusOutputString string
|
||||||
|
statusResp, err := getStatus(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrf("Failed to get status: %v\n", err)
|
||||||
|
} else {
|
||||||
|
statusOutputString = parseToFullDetailSummary(convertToStatusOutputOverview(statusResp))
|
||||||
|
}
|
||||||
|
return statusOutputString
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForDurationOrCancel(ctx context.Context, duration time.Duration, cmd *cobra.Command) error {
|
||||||
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
if elapsed >= duration {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remaining := duration - elapsed
|
||||||
|
cmd.Printf("\rRemaining time: %s", formatDuration(remaining))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
d = d.Round(time.Second)
|
||||||
|
h := d / time.Hour
|
||||||
|
d %= time.Hour
|
||||||
|
m := d / time.Minute
|
||||||
|
d %= time.Minute
|
||||||
|
s := d / time.Second
|
||||||
|
return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ const (
|
|||||||
preSharedKeyFlag = "preshared-key"
|
preSharedKeyFlag = "preshared-key"
|
||||||
interfaceNameFlag = "interface-name"
|
interfaceNameFlag = "interface-name"
|
||||||
wireguardPortFlag = "wireguard-port"
|
wireguardPortFlag = "wireguard-port"
|
||||||
|
networkMonitorFlag = "network-monitor"
|
||||||
disableAutoConnectFlag = "disable-auto-connect"
|
disableAutoConnectFlag = "disable-auto-connect"
|
||||||
serverSSHAllowedFlag = "allow-server-ssh"
|
serverSSHAllowedFlag = "allow-server-ssh"
|
||||||
extraIFaceBlackListFlag = "extra-iface-blacklist"
|
extraIFaceBlackListFlag = "extra-iface-blacklist"
|
||||||
@@ -62,9 +63,11 @@ var (
|
|||||||
serverSSHAllowed bool
|
serverSSHAllowed bool
|
||||||
interfaceName string
|
interfaceName string
|
||||||
wireguardPort uint16
|
wireguardPort uint16
|
||||||
|
networkMonitor bool
|
||||||
serviceName string
|
serviceName string
|
||||||
autoConnectDisabled bool
|
autoConnectDisabled bool
|
||||||
extraIFaceBlackList []string
|
extraIFaceBlackList []string
|
||||||
|
anonymizeFlag bool
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird",
|
Use: "netbird",
|
||||||
Short: "",
|
Short: "",
|
||||||
@@ -119,6 +122,8 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
||||||
rootCmd.PersistentFlags().StringVar(&preSharedKey, preSharedKeyFlag, "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
|
rootCmd.PersistentFlags().StringVar(&preSharedKey, preSharedKeyFlag, "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
|
||||||
rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device")
|
rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&anonymizeFlag, "anonymize", "A", false, "anonymize IP addresses and non-netbird.io domains in logs and status output")
|
||||||
|
|
||||||
rootCmd.AddCommand(serviceCmd)
|
rootCmd.AddCommand(serviceCmd)
|
||||||
rootCmd.AddCommand(upCmd)
|
rootCmd.AddCommand(upCmd)
|
||||||
rootCmd.AddCommand(downCmd)
|
rootCmd.AddCommand(downCmd)
|
||||||
@@ -126,8 +131,20 @@ func init() {
|
|||||||
rootCmd.AddCommand(loginCmd)
|
rootCmd.AddCommand(loginCmd)
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
rootCmd.AddCommand(sshCmd)
|
rootCmd.AddCommand(sshCmd)
|
||||||
|
rootCmd.AddCommand(routesCmd)
|
||||||
|
rootCmd.AddCommand(debugCmd)
|
||||||
|
|
||||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||||
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
||||||
|
|
||||||
|
routesCmd.AddCommand(routesListCmd)
|
||||||
|
routesCmd.AddCommand(routesSelectCmd, routesDeselectCmd)
|
||||||
|
|
||||||
|
debugCmd.AddCommand(debugBundleCmd)
|
||||||
|
debugCmd.AddCommand(logCmd)
|
||||||
|
logCmd.AddCommand(logLevelCmd)
|
||||||
|
debugCmd.AddCommand(forCmd)
|
||||||
|
|
||||||
upCmd.PersistentFlags().StringSliceVar(&natExternalIPs, externalIPMapFlag, nil,
|
upCmd.PersistentFlags().StringSliceVar(&natExternalIPs, externalIPMapFlag, nil,
|
||||||
`Sets external IPs maps between local addresses and interfaces.`+
|
`Sets external IPs maps between local addresses and interfaces.`+
|
||||||
`You can specify a comma-separated list with a single IP and IP/IP or IP/Interface Name. `+
|
`You can specify a comma-separated list with a single IP and IP/IP or IP/Interface Name. `+
|
||||||
@@ -335,3 +352,14 @@ func migrateToNetbird(oldPath, newPath string) bool {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getClient(ctx context.Context) (*grpc.ClientConn, error) {
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to daemon error: %v\n"+
|
||||||
|
"If the daemon is not running please run: "+
|
||||||
|
"\nnetbird service install \nnetbird service start\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|||||||
131
client/cmd/route.go
Normal file
131
client/cmd/route.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var appendFlag bool
|
||||||
|
|
||||||
|
var routesCmd = &cobra.Command{
|
||||||
|
Use: "routes",
|
||||||
|
Short: "Manage network routes",
|
||||||
|
Long: `Commands to list, select, or deselect network routes.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var routesListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "List routes",
|
||||||
|
Example: " netbird routes list",
|
||||||
|
Long: "List all available network routes.",
|
||||||
|
RunE: routesList,
|
||||||
|
}
|
||||||
|
|
||||||
|
var routesSelectCmd = &cobra.Command{
|
||||||
|
Use: "select route...|all",
|
||||||
|
Short: "Select routes",
|
||||||
|
Long: "Select a list of routes by identifiers or 'all' to clear all selections and to accept all (including new) routes.\nDefault mode is replace, use -a to append to already selected routes.",
|
||||||
|
Example: " netbird routes select all\n netbird routes select route1 route2\n netbird routes select -a route3",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: routesSelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
var routesDeselectCmd = &cobra.Command{
|
||||||
|
Use: "deselect route...|all",
|
||||||
|
Short: "Deselect routes",
|
||||||
|
Long: "Deselect previously selected routes by identifiers or 'all' to disable accepting any routes.",
|
||||||
|
Example: " netbird routes deselect all\n netbird routes deselect route1 route2",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: routesDeselect,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
routesSelectCmd.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "Append to current route selection instead of replacing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesList(cmd *cobra.Command, _ []string) error {
|
||||||
|
conn, err := getClient(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
resp, err := client.ListRoutes(cmd.Context(), &proto.ListRoutesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list routes: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Routes) == 0 {
|
||||||
|
cmd.Println("No routes available.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println("Available Routes:")
|
||||||
|
for _, route := range resp.Routes {
|
||||||
|
selectedStatus := "Not Selected"
|
||||||
|
if route.GetSelected() {
|
||||||
|
selectedStatus = "Selected"
|
||||||
|
}
|
||||||
|
cmd.Printf("\n - ID: %s\n Network: %s\n Status: %s\n", route.GetID(), route.GetNetwork(), selectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesSelect(cmd *cobra.Command, args []string) error {
|
||||||
|
conn, err := getClient(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
req := &proto.SelectRoutesRequest{
|
||||||
|
RouteIDs: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 1 && args[0] == "all" {
|
||||||
|
req.All = true
|
||||||
|
} else if appendFlag {
|
||||||
|
req.Append = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.SelectRoutes(cmd.Context(), req); err != nil {
|
||||||
|
return fmt.Errorf("failed to select routes: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println("Routes selected successfully.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesDeselect(cmd *cobra.Command, args []string) error {
|
||||||
|
conn, err := getClient(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
req := &proto.SelectRoutesRequest{
|
||||||
|
RouteIDs: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 1 && args[0] == "all" {
|
||||||
|
req.All = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.DeselectRoutes(cmd.Context(), req); err != nil {
|
||||||
|
return fmt.Errorf("failed to deselect routes: %v", status.Convert(err).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println("Routes deselected successfully.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var sshCmd = &cobra.Command{
|
var sshCmd = &cobra.Command{
|
||||||
Use: "ssh",
|
Use: "ssh [user@]host",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return errors.New("requires a host argument")
|
return errors.New("requires a host argument")
|
||||||
@@ -94,7 +94,7 @@ func runSSH(ctx context.Context, addr string, pemKey []byte, cmd *cobra.Command)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Printf("Error: %v\n", err)
|
cmd.Printf("Error: %v\n", err)
|
||||||
cmd.Printf("Couldn't connect. Please check the connection status or if the ssh server is enabled on the other peer" +
|
cmd.Printf("Couldn't connect. Please check the connection status or if the ssh server is enabled on the other peer" +
|
||||||
"You can verify the connection by running:\n\n" +
|
"\nYou can verify the connection by running:\n\n" +
|
||||||
" netbird status\n\n")
|
" netbird status\n\n")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/anonymize"
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
@@ -144,9 +147,9 @@ func statusFunc(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("failed initializing log %v", err)
|
return fmt.Errorf("failed initializing log %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := internal.CtxInitState(context.Background())
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
|
||||||
resp, err := getStatus(ctx, cmd)
|
resp, err := getStatus(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -191,7 +194,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatus(ctx context.Context, cmd *cobra.Command) (*proto.StatusResponse, error) {
|
func getStatus(ctx context.Context) (*proto.StatusResponse, error) {
|
||||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to connect to daemon error: %v\n"+
|
return nil, fmt.Errorf("failed to connect to daemon error: %v\n"+
|
||||||
@@ -200,7 +203,7 @@ func getStatus(ctx context.Context, cmd *cobra.Command) (*proto.StatusResponse,
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{GetFullPeerStatus: true})
|
resp, err := proto.NewDaemonServiceClient(conn).Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("status failed: %v", status.Convert(err).Message())
|
return nil, fmt.Errorf("status failed: %v", status.Convert(err).Message())
|
||||||
}
|
}
|
||||||
@@ -283,6 +286,11 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
|
|||||||
NSServerGroups: mapNSGroups(pbFullStatus.GetDnsServers()),
|
NSServerGroups: mapNSGroups(pbFullStatus.GetDnsServers()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if anonymizeFlag {
|
||||||
|
anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses())
|
||||||
|
anonymizeOverview(anonymizer, &overview)
|
||||||
|
}
|
||||||
|
|
||||||
return overview
|
return overview
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,8 +533,16 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
|
|
||||||
peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total)
|
peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total)
|
||||||
|
|
||||||
|
goos := runtime.GOOS
|
||||||
|
goarch := runtime.GOARCH
|
||||||
|
goarm := ""
|
||||||
|
if goarch == "arm" {
|
||||||
|
goarm = fmt.Sprintf(" (ARMv%s)", os.Getenv("GOARM"))
|
||||||
|
}
|
||||||
|
|
||||||
summary := fmt.Sprintf(
|
summary := fmt.Sprintf(
|
||||||
"Daemon version: %s\n"+
|
"OS: %s\n"+
|
||||||
|
"Daemon version: %s\n"+
|
||||||
"CLI version: %s\n"+
|
"CLI version: %s\n"+
|
||||||
"Management: %s\n"+
|
"Management: %s\n"+
|
||||||
"Signal: %s\n"+
|
"Signal: %s\n"+
|
||||||
@@ -538,6 +554,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
"Quantum resistance: %s\n"+
|
"Quantum resistance: %s\n"+
|
||||||
"Routes: %s\n"+
|
"Routes: %s\n"+
|
||||||
"Peers count: %s\n",
|
"Peers count: %s\n",
|
||||||
|
fmt.Sprintf("%s/%s%s", goos, goarch, goarm),
|
||||||
overview.DaemonVersion,
|
overview.DaemonVersion,
|
||||||
version.NetbirdVersion(),
|
version.NetbirdVersion(),
|
||||||
managementConnString,
|
managementConnString,
|
||||||
@@ -593,15 +610,6 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
if peerState.IceCandidateEndpoint.Remote != "" {
|
if peerState.IceCandidateEndpoint.Remote != "" {
|
||||||
remoteICEEndpoint = peerState.IceCandidateEndpoint.Remote
|
remoteICEEndpoint = peerState.IceCandidateEndpoint.Remote
|
||||||
}
|
}
|
||||||
lastStatusUpdate := "-"
|
|
||||||
if !peerState.LastStatusUpdate.IsZero() {
|
|
||||||
lastStatusUpdate = peerState.LastStatusUpdate.Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
lastWireGuardHandshake := "-"
|
|
||||||
if !peerState.LastWireguardHandshake.IsZero() && peerState.LastWireguardHandshake != time.Unix(0, 0) {
|
|
||||||
lastWireGuardHandshake = peerState.LastWireguardHandshake.Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
rosenpassEnabledStatus := "false"
|
rosenpassEnabledStatus := "false"
|
||||||
if rosenpassEnabled {
|
if rosenpassEnabled {
|
||||||
@@ -652,8 +660,8 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
remoteICE,
|
remoteICE,
|
||||||
localICEEndpoint,
|
localICEEndpoint,
|
||||||
remoteICEEndpoint,
|
remoteICEEndpoint,
|
||||||
lastStatusUpdate,
|
timeAgo(peerState.LastStatusUpdate),
|
||||||
lastWireGuardHandshake,
|
timeAgo(peerState.LastWireguardHandshake),
|
||||||
toIEC(peerState.TransferReceived),
|
toIEC(peerState.TransferReceived),
|
||||||
toIEC(peerState.TransferSent),
|
toIEC(peerState.TransferSent),
|
||||||
rosenpassEnabledStatus,
|
rosenpassEnabledStatus,
|
||||||
@@ -722,3 +730,129 @@ func countEnabled(dnsServers []nsServerGroupStateOutput) int {
|
|||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeAgo returns a string representing the duration since the provided time in a human-readable format.
|
||||||
|
func timeAgo(t time.Time) string {
|
||||||
|
if t.IsZero() || t.Equal(time.Unix(0, 0)) {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
duration := time.Since(t)
|
||||||
|
switch {
|
||||||
|
case duration < time.Second:
|
||||||
|
return "Now"
|
||||||
|
case duration < time.Minute:
|
||||||
|
seconds := int(duration.Seconds())
|
||||||
|
if seconds == 1 {
|
||||||
|
return "1 second ago"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d seconds ago", seconds)
|
||||||
|
case duration < time.Hour:
|
||||||
|
minutes := int(duration.Minutes())
|
||||||
|
seconds := int(duration.Seconds()) % 60
|
||||||
|
if minutes == 1 {
|
||||||
|
if seconds == 1 {
|
||||||
|
return "1 minute, 1 second ago"
|
||||||
|
} else if seconds > 0 {
|
||||||
|
return fmt.Sprintf("1 minute, %d seconds ago", seconds)
|
||||||
|
}
|
||||||
|
return "1 minute ago"
|
||||||
|
}
|
||||||
|
if seconds > 0 {
|
||||||
|
return fmt.Sprintf("%d minutes, %d seconds ago", minutes, seconds)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d minutes ago", minutes)
|
||||||
|
case duration < 24*time.Hour:
|
||||||
|
hours := int(duration.Hours())
|
||||||
|
minutes := int(duration.Minutes()) % 60
|
||||||
|
if hours == 1 {
|
||||||
|
if minutes == 1 {
|
||||||
|
return "1 hour, 1 minute ago"
|
||||||
|
} else if minutes > 0 {
|
||||||
|
return fmt.Sprintf("1 hour, %d minutes ago", minutes)
|
||||||
|
}
|
||||||
|
return "1 hour ago"
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
return fmt.Sprintf("%d hours, %d minutes ago", hours, minutes)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d hours ago", hours)
|
||||||
|
}
|
||||||
|
|
||||||
|
days := int(duration.Hours()) / 24
|
||||||
|
hours := int(duration.Hours()) % 24
|
||||||
|
if days == 1 {
|
||||||
|
if hours == 1 {
|
||||||
|
return "1 day, 1 hour ago"
|
||||||
|
} else if hours > 0 {
|
||||||
|
return fmt.Sprintf("1 day, %d hours ago", hours)
|
||||||
|
}
|
||||||
|
return "1 day ago"
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
return fmt.Sprintf("%d days, %d hours ago", days, hours)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d days ago", days)
|
||||||
|
}
|
||||||
|
|
||||||
|
func anonymizePeerDetail(a *anonymize.Anonymizer, peer *peerStateDetailOutput) {
|
||||||
|
peer.FQDN = a.AnonymizeDomain(peer.FQDN)
|
||||||
|
if localIP, port, err := net.SplitHostPort(peer.IceCandidateEndpoint.Local); err == nil {
|
||||||
|
peer.IceCandidateEndpoint.Local = fmt.Sprintf("%s:%s", a.AnonymizeIPString(localIP), port)
|
||||||
|
}
|
||||||
|
if remoteIP, port, err := net.SplitHostPort(peer.IceCandidateEndpoint.Remote); err == nil {
|
||||||
|
peer.IceCandidateEndpoint.Remote = fmt.Sprintf("%s:%s", a.AnonymizeIPString(remoteIP), port)
|
||||||
|
}
|
||||||
|
for i, route := range peer.Routes {
|
||||||
|
peer.Routes[i] = a.AnonymizeIPString(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, route := range peer.Routes {
|
||||||
|
prefix, err := netip.ParsePrefix(route)
|
||||||
|
if err == nil {
|
||||||
|
ip := a.AnonymizeIPString(prefix.Addr().String())
|
||||||
|
peer.Routes[i] = fmt.Sprintf("%s/%d", ip, prefix.Bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func anonymizeOverview(a *anonymize.Anonymizer, overview *statusOutputOverview) {
|
||||||
|
for i, peer := range overview.Peers.Details {
|
||||||
|
peer := peer
|
||||||
|
anonymizePeerDetail(a, &peer)
|
||||||
|
overview.Peers.Details[i] = peer
|
||||||
|
}
|
||||||
|
|
||||||
|
overview.ManagementState.URL = a.AnonymizeURI(overview.ManagementState.URL)
|
||||||
|
overview.ManagementState.Error = a.AnonymizeString(overview.ManagementState.Error)
|
||||||
|
overview.SignalState.URL = a.AnonymizeURI(overview.SignalState.URL)
|
||||||
|
overview.SignalState.Error = a.AnonymizeString(overview.SignalState.Error)
|
||||||
|
|
||||||
|
overview.IP = a.AnonymizeIPString(overview.IP)
|
||||||
|
for i, detail := range overview.Relays.Details {
|
||||||
|
detail.URI = a.AnonymizeURI(detail.URI)
|
||||||
|
detail.Error = a.AnonymizeString(detail.Error)
|
||||||
|
overview.Relays.Details[i] = detail
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, nsGroup := range overview.NSServerGroups {
|
||||||
|
for j, domain := range nsGroup.Domains {
|
||||||
|
overview.NSServerGroups[i].Domains[j] = a.AnonymizeDomain(domain)
|
||||||
|
}
|
||||||
|
for j, ns := range nsGroup.Servers {
|
||||||
|
host, port, err := net.SplitHostPort(ns)
|
||||||
|
if err == nil {
|
||||||
|
overview.NSServerGroups[i].Servers[j] = fmt.Sprintf("%s:%s", a.AnonymizeIPString(host), port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, route := range overview.Routes {
|
||||||
|
prefix, err := netip.ParsePrefix(route)
|
||||||
|
if err == nil {
|
||||||
|
ip := a.AnonymizeIPString(prefix.Addr().String())
|
||||||
|
overview.Routes[i] = fmt.Sprintf("%s/%d", ip, prefix.Bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overview.FQDN = a.AnonymizeDomain(overview.FQDN)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -487,9 +489,15 @@ dnsServers:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParsingToDetail(t *testing.T) {
|
func TestParsingToDetail(t *testing.T) {
|
||||||
|
// Calculate time ago based on the fixture dates
|
||||||
|
lastConnectionUpdate1 := timeAgo(overview.Peers.Details[0].LastStatusUpdate)
|
||||||
|
lastHandshake1 := timeAgo(overview.Peers.Details[0].LastWireguardHandshake)
|
||||||
|
lastConnectionUpdate2 := timeAgo(overview.Peers.Details[1].LastStatusUpdate)
|
||||||
|
lastHandshake2 := timeAgo(overview.Peers.Details[1].LastWireguardHandshake)
|
||||||
|
|
||||||
detail := parseToFullDetailSummary(overview)
|
detail := parseToFullDetailSummary(overview)
|
||||||
|
|
||||||
expectedDetail :=
|
expectedDetail := fmt.Sprintf(
|
||||||
`Peers detail:
|
`Peers detail:
|
||||||
peer-1.awesome-domain.com:
|
peer-1.awesome-domain.com:
|
||||||
NetBird IP: 192.168.178.101
|
NetBird IP: 192.168.178.101
|
||||||
@@ -500,8 +508,8 @@ func TestParsingToDetail(t *testing.T) {
|
|||||||
Direct: true
|
Direct: true
|
||||||
ICE candidate (Local/Remote): -/-
|
ICE candidate (Local/Remote): -/-
|
||||||
ICE candidate endpoints (Local/Remote): -/-
|
ICE candidate endpoints (Local/Remote): -/-
|
||||||
Last connection update: 2001-01-01 01:01:01
|
Last connection update: %s
|
||||||
Last WireGuard handshake: 2001-01-01 01:01:02
|
Last WireGuard handshake: %s
|
||||||
Transfer status (received/sent) 200 B/100 B
|
Transfer status (received/sent) 200 B/100 B
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: 10.1.0.0/24
|
Routes: 10.1.0.0/24
|
||||||
@@ -516,15 +524,16 @@ func TestParsingToDetail(t *testing.T) {
|
|||||||
Direct: false
|
Direct: false
|
||||||
ICE candidate (Local/Remote): relay/prflx
|
ICE candidate (Local/Remote): relay/prflx
|
||||||
ICE candidate endpoints (Local/Remote): 10.0.0.1:10001/10.0.10.1:10002
|
ICE candidate endpoints (Local/Remote): 10.0.0.1:10001/10.0.10.1:10002
|
||||||
Last connection update: 2002-02-02 02:02:02
|
Last connection update: %s
|
||||||
Last WireGuard handshake: 2002-02-02 02:02:03
|
Last WireGuard handshake: %s
|
||||||
Transfer status (received/sent) 2.0 KiB/1000 B
|
Transfer status (received/sent) 2.0 KiB/1000 B
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: -
|
Routes: -
|
||||||
Latency: 10ms
|
Latency: 10ms
|
||||||
|
|
||||||
|
OS: %s/%s
|
||||||
Daemon version: 0.14.1
|
Daemon version: 0.14.1
|
||||||
CLI version: development
|
CLI version: %s
|
||||||
Management: Connected to my-awesome-management.com:443
|
Management: Connected to my-awesome-management.com:443
|
||||||
Signal: Connected to my-awesome-signal.com:443
|
Signal: Connected to my-awesome-signal.com:443
|
||||||
Relays:
|
Relays:
|
||||||
@@ -539,7 +548,7 @@ Interface type: Kernel
|
|||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
Routes: 10.10.0.0/24
|
Routes: 10.10.0.0/24
|
||||||
Peers count: 2/2 Connected
|
Peers count: 2/2 Connected
|
||||||
`
|
`, lastConnectionUpdate1, lastHandshake1, lastConnectionUpdate2, lastHandshake2, runtime.GOOS, runtime.GOARCH, overview.CliVersion)
|
||||||
|
|
||||||
assert.Equal(t, expectedDetail, detail)
|
assert.Equal(t, expectedDetail, detail)
|
||||||
}
|
}
|
||||||
@@ -547,8 +556,8 @@ Peers count: 2/2 Connected
|
|||||||
func TestParsingToShortVersion(t *testing.T) {
|
func TestParsingToShortVersion(t *testing.T) {
|
||||||
shortVersion := parseGeneralSummary(overview, false, false, false)
|
shortVersion := parseGeneralSummary(overview, false, false, false)
|
||||||
|
|
||||||
expectedString :=
|
expectedString := fmt.Sprintf("OS: %s/%s", runtime.GOOS, runtime.GOARCH) + `
|
||||||
`Daemon version: 0.14.1
|
Daemon version: 0.14.1
|
||||||
CLI version: development
|
CLI version: development
|
||||||
Management: Connected
|
Management: Connected
|
||||||
Signal: Connected
|
Signal: Connected
|
||||||
@@ -572,3 +581,31 @@ func TestParsingOfIP(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "192.168.178.123\n", parsedIP)
|
assert.Equal(t, "192.168.178.123\n", parsedIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimeAgo(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input time.Time
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"Now", now, "Now"},
|
||||||
|
{"Seconds ago", now.Add(-10 * time.Second), "10 seconds ago"},
|
||||||
|
{"One minute ago", now.Add(-1 * time.Minute), "1 minute ago"},
|
||||||
|
{"Minutes and seconds ago", now.Add(-(1*time.Minute + 30*time.Second)), "1 minute, 30 seconds ago"},
|
||||||
|
{"One hour ago", now.Add(-1 * time.Hour), "1 hour ago"},
|
||||||
|
{"Hours and minutes ago", now.Add(-(2*time.Hour + 15*time.Minute)), "2 hours, 15 minutes ago"},
|
||||||
|
{"One day ago", now.Add(-24 * time.Hour), "1 day ago"},
|
||||||
|
{"Multiple days ago", now.Add(-(72*time.Hour + 20*time.Minute)), "3 days ago"},
|
||||||
|
{"Zero time", time.Time{}, "-"},
|
||||||
|
{"Unix zero time", time.Unix(0, 0), "-"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := timeAgo(tc.input)
|
||||||
|
assert.Equal(t, tc.expected, result, "Failed %s", tc.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
clientProto "github.com/netbirdio/netbird/client/proto"
|
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||||
client "github.com/netbirdio/netbird/client/server"
|
client "github.com/netbirdio/netbird/client/server"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
@@ -69,10 +70,11 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
store, err := mgmt.NewStoreFromJson(config.Datadir, nil)
|
store, cleanUp, err := mgmt.NewTestStoreFromJson(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func init() {
|
|||||||
upCmd.PersistentFlags().BoolVarP(&foregroundMode, "foreground-mode", "F", false, "start service in foreground")
|
upCmd.PersistentFlags().BoolVarP(&foregroundMode, "foreground-mode", "F", false, "start service in foreground")
|
||||||
upCmd.PersistentFlags().StringVar(&interfaceName, interfaceNameFlag, iface.WgInterfaceDefault, "Wireguard interface name")
|
upCmd.PersistentFlags().StringVar(&interfaceName, interfaceNameFlag, iface.WgInterfaceDefault, "Wireguard interface name")
|
||||||
upCmd.PersistentFlags().Uint16Var(&wireguardPort, wireguardPortFlag, iface.DefaultWgPort, "Wireguard interface listening port")
|
upCmd.PersistentFlags().Uint16Var(&wireguardPort, wireguardPortFlag, iface.DefaultWgPort, "Wireguard interface listening port")
|
||||||
|
upCmd.PersistentFlags().BoolVarP(&networkMonitor, networkMonitorFlag, "N", false, "Enable network monitoring")
|
||||||
upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening")
|
upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +117,10 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
ic.WireguardPort = &p
|
ic.WireguardPort = &p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cmd.Flag(networkMonitorFlag).Changed {
|
||||||
|
ic.NetworkMonitor = &networkMonitor
|
||||||
|
}
|
||||||
|
|
||||||
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
|
||||||
ic.PreSharedKey = &preSharedKey
|
ic.PreSharedKey = &preSharedKey
|
||||||
}
|
}
|
||||||
@@ -147,7 +152,9 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithCancel(ctx)
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
SetupCloseHandler(ctx, cancel)
|
SetupCloseHandler(ctx, cancel)
|
||||||
return internal.RunClient(ctx, config, peer.NewRecorder(config.ManagementURL.String()))
|
|
||||||
|
connectClient := internal.NewConnectClient(ctx, config, peer.NewRecorder(config.ManagementURL.String()))
|
||||||
|
return connectClient.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
||||||
@@ -226,6 +233,10 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
loginRequest.WireguardPort = &wp
|
loginRequest.WireguardPort = &wp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cmd.Flag(networkMonitorFlag).Changed {
|
||||||
|
loginRequest.NetworkMonitor = &networkMonitor
|
||||||
|
}
|
||||||
|
|
||||||
var loginErr error
|
var loginErr error
|
||||||
|
|
||||||
var loginResp *proto.LoginResponse
|
var loginResp *proto.LoginResponse
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ func (i *routerManager) InsertRoutingRules(pair firewall.RouterPair) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertRoutingRule inserts an iptable rule
|
// insertRoutingRule inserts an iptables rule
|
||||||
func (i *routerManager) insertRoutingRule(keyFormat, table, chain, jump string, pair firewall.RouterPair) error {
|
func (i *routerManager) insertRoutingRule(keyFormat, table, chain, jump string, pair firewall.RouterPair) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ruleKey := firewall.GenKey(keyFormat, pair.ID)
|
ruleKey := firewall.GenKey(keyFormat, pair.ID)
|
||||||
rule := genRuleSpec(jump, ruleKey, pair.Source, pair.Destination)
|
rule := genRuleSpec(jump, pair.Source, pair.Destination)
|
||||||
existingRule, found := i.rules[ruleKey]
|
existingRule, found := i.rules[ruleKey]
|
||||||
if found {
|
if found {
|
||||||
err = i.iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
err = i.iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
||||||
@@ -326,9 +326,9 @@ func (i *routerManager) createChain(table, newChain string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// genRuleSpec generates rule specification with comment identifier
|
// genRuleSpec generates rule specification
|
||||||
func genRuleSpec(jump, id, source, destination string) []string {
|
func genRuleSpec(jump, source, destination string) []string {
|
||||||
return []string{"-s", source, "-d", destination, "-j", jump, "-m", "comment", "--comment", id}
|
return []string{"-s", source, "-d", destination, "-j", jump}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIptablesRuleType(table string) string {
|
func getIptablesRuleType(table string) string {
|
||||||
|
|||||||
@@ -51,14 +51,12 @@ func TestIptablesManager_RestoreOrCreateContainers(t *testing.T) {
|
|||||||
Destination: "100.100.100.0/24",
|
Destination: "100.100.100.0/24",
|
||||||
Masquerade: true,
|
Masquerade: true,
|
||||||
}
|
}
|
||||||
forward4RuleKey := firewall.GenKey(firewall.ForwardingFormat, pair.ID)
|
forward4Rule := genRuleSpec(routingFinalForwardJump, pair.Source, pair.Destination)
|
||||||
forward4Rule := genRuleSpec(routingFinalForwardJump, forward4RuleKey, pair.Source, pair.Destination)
|
|
||||||
|
|
||||||
err = manager.iptablesClient.Insert(tableFilter, chainRTFWD, 1, forward4Rule...)
|
err = manager.iptablesClient.Insert(tableFilter, chainRTFWD, 1, forward4Rule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
nat4RuleKey := firewall.GenKey(firewall.NatFormat, pair.ID)
|
nat4Rule := genRuleSpec(routingFinalNatJump, pair.Source, pair.Destination)
|
||||||
nat4Rule := genRuleSpec(routingFinalNatJump, nat4RuleKey, pair.Source, pair.Destination)
|
|
||||||
|
|
||||||
err = manager.iptablesClient.Insert(tableNat, chainRTNAT, 1, nat4Rule...)
|
err = manager.iptablesClient.Insert(tableNat, chainRTNAT, 1, nat4Rule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
@@ -92,7 +90,7 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
require.NoError(t, err, "forwarding pair should be inserted")
|
require.NoError(t, err, "forwarding pair should be inserted")
|
||||||
|
|
||||||
forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
||||||
forwardRule := genRuleSpec(routingFinalForwardJump, forwardRuleKey, testCase.InputPair.Source, testCase.InputPair.Destination)
|
forwardRule := genRuleSpec(routingFinalForwardJump, testCase.InputPair.Source, testCase.InputPair.Destination)
|
||||||
|
|
||||||
exists, err := iptablesClient.Exists(tableFilter, chainRTFWD, forwardRule...)
|
exists, err := iptablesClient.Exists(tableFilter, chainRTFWD, forwardRule...)
|
||||||
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableFilter, chainRTFWD)
|
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableFilter, chainRTFWD)
|
||||||
@@ -103,7 +101,7 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
require.Equal(t, forwardRule[:4], foundRule[:4], "stored forwarding rule should match")
|
require.Equal(t, forwardRule[:4], foundRule[:4], "stored forwarding rule should match")
|
||||||
|
|
||||||
inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
||||||
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
inForwardRule := genRuleSpec(routingFinalForwardJump, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
||||||
|
|
||||||
exists, err = iptablesClient.Exists(tableFilter, chainRTFWD, inForwardRule...)
|
exists, err = iptablesClient.Exists(tableFilter, chainRTFWD, inForwardRule...)
|
||||||
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableFilter, chainRTFWD)
|
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableFilter, chainRTFWD)
|
||||||
@@ -114,7 +112,7 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
require.Equal(t, inForwardRule[:4], foundRule[:4], "stored income forwarding rule should match")
|
require.Equal(t, inForwardRule[:4], foundRule[:4], "stored income forwarding rule should match")
|
||||||
|
|
||||||
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
||||||
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.InputPair.Source, testCase.InputPair.Destination)
|
natRule := genRuleSpec(routingFinalNatJump, testCase.InputPair.Source, testCase.InputPair.Destination)
|
||||||
|
|
||||||
exists, err = iptablesClient.Exists(tableNat, chainRTNAT, natRule...)
|
exists, err = iptablesClient.Exists(tableNat, chainRTNAT, natRule...)
|
||||||
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableNat, chainRTNAT)
|
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableNat, chainRTNAT)
|
||||||
@@ -130,7 +128,7 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
||||||
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
inNatRule := genRuleSpec(routingFinalNatJump, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
||||||
|
|
||||||
exists, err = iptablesClient.Exists(tableNat, chainRTNAT, inNatRule...)
|
exists, err = iptablesClient.Exists(tableNat, chainRTNAT, inNatRule...)
|
||||||
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableNat, chainRTNAT)
|
require.NoError(t, err, "should be able to query the iptables %s table and %s chain", tableNat, chainRTNAT)
|
||||||
@@ -167,25 +165,25 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
require.NoError(t, err, "shouldn't return error")
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
||||||
forwardRule := genRuleSpec(routingFinalForwardJump, forwardRuleKey, testCase.InputPair.Source, testCase.InputPair.Destination)
|
forwardRule := genRuleSpec(routingFinalForwardJump, testCase.InputPair.Source, testCase.InputPair.Destination)
|
||||||
|
|
||||||
err = iptablesClient.Insert(tableFilter, chainRTFWD, 1, forwardRule...)
|
err = iptablesClient.Insert(tableFilter, chainRTFWD, 1, forwardRule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
||||||
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
inForwardRule := genRuleSpec(routingFinalForwardJump, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
||||||
|
|
||||||
err = iptablesClient.Insert(tableFilter, chainRTFWD, 1, inForwardRule...)
|
err = iptablesClient.Insert(tableFilter, chainRTFWD, 1, inForwardRule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
||||||
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.InputPair.Source, testCase.InputPair.Destination)
|
natRule := genRuleSpec(routingFinalNatJump, testCase.InputPair.Source, testCase.InputPair.Destination)
|
||||||
|
|
||||||
err = iptablesClient.Insert(tableNat, chainRTNAT, 1, natRule...)
|
err = iptablesClient.Insert(tableNat, chainRTNAT, 1, natRule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
||||||
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
inNatRule := genRuleSpec(routingFinalNatJump, firewall.GetInPair(testCase.InputPair).Source, firewall.GetInPair(testCase.InputPair).Destination)
|
||||||
|
|
||||||
err = iptablesClient.Insert(tableNat, chainRTNAT, 1, inNatRule...)
|
err = iptablesClient.Insert(tableNat, chainRTNAT, 1, inNatRule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|||||||
@@ -64,15 +64,18 @@ func manageFirewallRule(ruleName string, action action, extraArgs ...string) err
|
|||||||
if action == addRule {
|
if action == addRule {
|
||||||
args = append(args, extraArgs...)
|
args = append(args, extraArgs...)
|
||||||
}
|
}
|
||||||
|
netshCmd := GetSystem32Command("netsh")
|
||||||
cmd := exec.Command("netsh", args...)
|
cmd := exec.Command(netshCmd, args...)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWindowsFirewallReachable() bool {
|
func isWindowsFirewallReachable() bool {
|
||||||
args := []string{"advfirewall", "show", "allprofiles", "state"}
|
args := []string{"advfirewall", "show", "allprofiles", "state"}
|
||||||
cmd := exec.Command("netsh", args...)
|
|
||||||
|
netshCmd := GetSystem32Command("netsh")
|
||||||
|
|
||||||
|
cmd := exec.Command(netshCmd, args...)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
|
||||||
_, err := cmd.Output()
|
_, err := cmd.Output()
|
||||||
@@ -87,8 +90,23 @@ func isWindowsFirewallReachable() bool {
|
|||||||
func isFirewallRuleActive(ruleName string) bool {
|
func isFirewallRuleActive(ruleName string) bool {
|
||||||
args := []string{"advfirewall", "firewall", "show", "rule", "name=" + ruleName}
|
args := []string{"advfirewall", "firewall", "show", "rule", "name=" + ruleName}
|
||||||
|
|
||||||
cmd := exec.Command("netsh", args...)
|
netshCmd := GetSystem32Command("netsh")
|
||||||
|
|
||||||
|
cmd := exec.Command(netshCmd, args...)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
_, err := cmd.Output()
|
_, err := cmd.Output()
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSystem32Command checks if a command can be found in the system path and returns it. In case it can't find it
|
||||||
|
// in the path it will return the full path of a command assuming C:\windows\system32 as the base path.
|
||||||
|
func GetSystem32Command(command string) string {
|
||||||
|
_, err := exec.LookPath(command)
|
||||||
|
if err == nil {
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("Command %s not found in PATH, using C:\\windows\\system32\\%s.exe path", command, command)
|
||||||
|
|
||||||
|
return "C:\\windows\\system32\\" + command + ".exe"
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
@@ -48,6 +50,7 @@ type ConfigInput struct {
|
|||||||
RosenpassPermissive *bool
|
RosenpassPermissive *bool
|
||||||
InterfaceName *string
|
InterfaceName *string
|
||||||
WireguardPort *int
|
WireguardPort *int
|
||||||
|
NetworkMonitor *bool
|
||||||
DisableAutoConnect *bool
|
DisableAutoConnect *bool
|
||||||
ExtraIFaceBlackList []string
|
ExtraIFaceBlackList []string
|
||||||
}
|
}
|
||||||
@@ -61,6 +64,7 @@ type Config struct {
|
|||||||
AdminURL *url.URL
|
AdminURL *url.URL
|
||||||
WgIface string
|
WgIface string
|
||||||
WgPort int
|
WgPort int
|
||||||
|
NetworkMonitor bool
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
DisableIPv6Discovery bool
|
DisableIPv6Discovery bool
|
||||||
RosenpassEnabled bool
|
RosenpassEnabled bool
|
||||||
@@ -100,6 +104,14 @@ func ReadConfig(configPath string) (*Config, error) {
|
|||||||
if _, err := util.ReadJson(configPath, config); err != nil {
|
if _, err := util.ReadJson(configPath, config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// initialize through apply() without changes
|
||||||
|
if changed, err := config.apply(ConfigInput{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if changed {
|
||||||
|
if err = WriteOutConfig(configPath, config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
@@ -152,79 +164,15 @@ func WriteOutConfig(path string, config *Config) error {
|
|||||||
|
|
||||||
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||||
func createNewConfig(input ConfigInput) (*Config, error) {
|
func createNewConfig(input ConfigInput) (*Config, error) {
|
||||||
wgKey := generateKey()
|
|
||||||
pem, err := ssh.GeneratePrivateKey(ssh.ED25519)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &Config{
|
config := &Config{
|
||||||
SSHKey: string(pem),
|
// defaults to false only for new (post 0.26) configurations
|
||||||
PrivateKey: wgKey,
|
ServerSSHAllowed: util.False(),
|
||||||
IFaceBlackList: []string{},
|
|
||||||
DisableIPv6Discovery: false,
|
|
||||||
NATExternalIPs: input.NATExternalIPs,
|
|
||||||
CustomDNSAddress: string(input.CustomDNSAddress),
|
|
||||||
ServerSSHAllowed: util.False(),
|
|
||||||
DisableAutoConnect: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultManagementURL, err := parseURL("Management URL", DefaultManagementURL)
|
if _, err := config.apply(input); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.ManagementURL = defaultManagementURL
|
|
||||||
if input.ManagementURL != "" {
|
|
||||||
URL, err := parseURL("Management URL", input.ManagementURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.ManagementURL = URL
|
|
||||||
}
|
|
||||||
|
|
||||||
config.WgPort = iface.DefaultWgPort
|
|
||||||
if input.WireguardPort != nil {
|
|
||||||
config.WgPort = *input.WireguardPort
|
|
||||||
}
|
|
||||||
|
|
||||||
config.WgIface = iface.WgInterfaceDefault
|
|
||||||
if input.InterfaceName != nil {
|
|
||||||
config.WgIface = *input.InterfaceName
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.PreSharedKey != nil {
|
|
||||||
config.PreSharedKey = *input.PreSharedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.RosenpassEnabled != nil {
|
|
||||||
config.RosenpassEnabled = *input.RosenpassEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.RosenpassPermissive != nil {
|
|
||||||
config.RosenpassPermissive = *input.RosenpassPermissive
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.ServerSSHAllowed != nil {
|
|
||||||
config.ServerSSHAllowed = input.ServerSSHAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultAdminURL, err := parseURL("Admin URL", DefaultAdminURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.AdminURL = defaultAdminURL
|
|
||||||
if input.AdminURL != "" {
|
|
||||||
newURL, err := parseURL("Admin Panel URL", input.AdminURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.AdminURL = newURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:gocritic
|
|
||||||
config.IFaceBlackList = append(defaultInterfaceBlacklist, input.ExtraIFaceBlackList...)
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,104 +183,12 @@ func update(input ConfigInput) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh := false
|
updated, err := config.apply(input)
|
||||||
|
if err != nil {
|
||||||
if input.ManagementURL != "" && config.ManagementURL.String() != input.ManagementURL {
|
return nil, err
|
||||||
log.Infof("new Management URL provided, updated to %s (old value %s)",
|
|
||||||
input.ManagementURL, config.ManagementURL)
|
|
||||||
newURL, err := parseURL("Management URL", input.ManagementURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.ManagementURL = newURL
|
|
||||||
refresh = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.AdminURL != "" && (config.AdminURL == nil || config.AdminURL.String() != input.AdminURL) {
|
if updated {
|
||||||
log.Infof("new Admin Panel URL provided, updated to %s (old value %s)",
|
|
||||||
input.AdminURL, config.AdminURL)
|
|
||||||
newURL, err := parseURL("Admin Panel URL", input.AdminURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.AdminURL = newURL
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.PreSharedKey != nil && config.PreSharedKey != *input.PreSharedKey {
|
|
||||||
log.Infof("new pre-shared key provided, replacing old key")
|
|
||||||
config.PreSharedKey = *input.PreSharedKey
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.SSHKey == "" {
|
|
||||||
pem, err := ssh.GeneratePrivateKey(ssh.ED25519)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.SSHKey = string(pem)
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.WgPort == 0 {
|
|
||||||
config.WgPort = iface.DefaultWgPort
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.WireguardPort != nil {
|
|
||||||
config.WgPort = *input.WireguardPort
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.InterfaceName != nil {
|
|
||||||
config.WgIface = *input.InterfaceName
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.NATExternalIPs != nil && len(config.NATExternalIPs) != len(input.NATExternalIPs) {
|
|
||||||
config.NATExternalIPs = input.NATExternalIPs
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.CustomDNSAddress != nil {
|
|
||||||
config.CustomDNSAddress = string(input.CustomDNSAddress)
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.RosenpassEnabled != nil {
|
|
||||||
config.RosenpassEnabled = *input.RosenpassEnabled
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.RosenpassPermissive != nil {
|
|
||||||
config.RosenpassPermissive = *input.RosenpassPermissive
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.DisableAutoConnect != nil {
|
|
||||||
config.DisableAutoConnect = *input.DisableAutoConnect
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.ServerSSHAllowed != nil {
|
|
||||||
config.ServerSSHAllowed = input.ServerSSHAllowed
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.ServerSSHAllowed == nil {
|
|
||||||
config.ServerSSHAllowed = util.True()
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(input.ExtraIFaceBlackList) > 0 {
|
|
||||||
for _, iFace := range util.SliceDiff(input.ExtraIFaceBlackList, config.IFaceBlackList) {
|
|
||||||
config.IFaceBlackList = append(config.IFaceBlackList, iFace)
|
|
||||||
refresh = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if refresh {
|
|
||||||
// since we have new management URL, we need to update config file
|
|
||||||
if err := util.WriteJson(input.ConfigPath, config); err != nil {
|
if err := util.WriteJson(input.ConfigPath, config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -341,6 +197,169 @@ func update(input ConfigInput) (*Config, error) {
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
||||||
|
if config.ManagementURL == nil {
|
||||||
|
log.Infof("using default Management URL %s", DefaultManagementURL)
|
||||||
|
config.ManagementURL, err = parseURL("Management URL", DefaultManagementURL)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if input.ManagementURL != "" && input.ManagementURL != config.ManagementURL.String() {
|
||||||
|
log.Infof("new Management URL provided, updated to %#v (old value %#v)",
|
||||||
|
input.ManagementURL, config.ManagementURL.String())
|
||||||
|
URL, err := parseURL("Management URL", input.ManagementURL)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
config.ManagementURL = URL
|
||||||
|
updated = true
|
||||||
|
} else if config.ManagementURL == nil {
|
||||||
|
log.Infof("using default Management URL %s", DefaultManagementURL)
|
||||||
|
config.ManagementURL, err = parseURL("Management URL", DefaultManagementURL)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.AdminURL == nil {
|
||||||
|
log.Infof("using default Admin URL %s", DefaultManagementURL)
|
||||||
|
config.AdminURL, err = parseURL("Admin URL", DefaultAdminURL)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if input.AdminURL != "" && input.AdminURL != config.AdminURL.String() {
|
||||||
|
log.Infof("new Admin Panel URL provided, updated to %#v (old value %#v)",
|
||||||
|
input.AdminURL, config.AdminURL.String())
|
||||||
|
newURL, err := parseURL("Admin Panel URL", input.AdminURL)
|
||||||
|
if err != nil {
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
config.AdminURL = newURL
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PrivateKey == "" {
|
||||||
|
log.Infof("generated new Wireguard key")
|
||||||
|
config.PrivateKey = generateKey()
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SSHKey == "" {
|
||||||
|
log.Infof("generated new SSH key")
|
||||||
|
pem, err := ssh.GeneratePrivateKey(ssh.ED25519)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
config.SSHKey = string(pem)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.WireguardPort != nil && *input.WireguardPort != config.WgPort {
|
||||||
|
log.Infof("updating Wireguard port %d (old value %d)",
|
||||||
|
*input.WireguardPort, config.WgPort)
|
||||||
|
config.WgPort = *input.WireguardPort
|
||||||
|
updated = true
|
||||||
|
} else if config.WgPort == 0 {
|
||||||
|
config.WgPort = iface.DefaultWgPort
|
||||||
|
log.Infof("using default Wireguard port %d", config.WgPort)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.InterfaceName != nil && *input.InterfaceName != config.WgIface {
|
||||||
|
log.Infof("updating Wireguard interface %#v (old value %#v)",
|
||||||
|
*input.InterfaceName, config.WgIface)
|
||||||
|
config.WgIface = *input.InterfaceName
|
||||||
|
updated = true
|
||||||
|
} else if config.WgIface == "" {
|
||||||
|
config.WgIface = iface.WgInterfaceDefault
|
||||||
|
log.Infof("using default Wireguard interface %s", config.WgIface)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.NATExternalIPs != nil && !reflect.DeepEqual(config.NATExternalIPs, input.NATExternalIPs) {
|
||||||
|
log.Infof("updating NAT External IP [ %s ] (old value: [ %s ])",
|
||||||
|
strings.Join(input.NATExternalIPs, " "),
|
||||||
|
strings.Join(config.NATExternalIPs, " "))
|
||||||
|
config.NATExternalIPs = input.NATExternalIPs
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.PreSharedKey != nil && *input.PreSharedKey != config.PreSharedKey {
|
||||||
|
log.Infof("new pre-shared key provided, replacing old key")
|
||||||
|
config.PreSharedKey = *input.PreSharedKey
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.RosenpassEnabled != nil && *input.RosenpassEnabled != config.RosenpassEnabled {
|
||||||
|
log.Infof("switching Rosenpass to %t", *input.RosenpassEnabled)
|
||||||
|
config.RosenpassEnabled = *input.RosenpassEnabled
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.RosenpassPermissive != nil && *input.RosenpassPermissive != config.RosenpassPermissive {
|
||||||
|
log.Infof("switching Rosenpass permissive to %t", *input.RosenpassPermissive)
|
||||||
|
config.RosenpassPermissive = *input.RosenpassPermissive
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.NetworkMonitor != nil && *input.NetworkMonitor != config.NetworkMonitor {
|
||||||
|
log.Infof("switching Network Monitor to %t", *input.NetworkMonitor)
|
||||||
|
config.NetworkMonitor = *input.NetworkMonitor
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.CustomDNSAddress != nil && string(input.CustomDNSAddress) != config.CustomDNSAddress {
|
||||||
|
log.Infof("updating custom DNS address %#v (old value %#v)",
|
||||||
|
string(input.CustomDNSAddress), config.CustomDNSAddress)
|
||||||
|
config.CustomDNSAddress = string(input.CustomDNSAddress)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.IFaceBlackList) == 0 {
|
||||||
|
log.Infof("filling in interface blacklist with defaults: [ %s ]",
|
||||||
|
strings.Join(defaultInterfaceBlacklist, " "))
|
||||||
|
config.IFaceBlackList = append(config.IFaceBlackList, defaultInterfaceBlacklist...)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(input.ExtraIFaceBlackList) > 0 {
|
||||||
|
for _, iFace := range util.SliceDiff(input.ExtraIFaceBlackList, config.IFaceBlackList) {
|
||||||
|
log.Infof("adding new entry to interface blacklist: %s", iFace)
|
||||||
|
config.IFaceBlackList = append(config.IFaceBlackList, iFace)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.DisableAutoConnect != nil && *input.DisableAutoConnect != config.DisableAutoConnect {
|
||||||
|
if *input.DisableAutoConnect {
|
||||||
|
log.Infof("turning off automatic connection on startup")
|
||||||
|
} else {
|
||||||
|
log.Infof("enabling automatic connection on startup")
|
||||||
|
}
|
||||||
|
config.DisableAutoConnect = *input.DisableAutoConnect
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.ServerSSHAllowed != nil && *input.ServerSSHAllowed != *config.ServerSSHAllowed {
|
||||||
|
if *input.ServerSSHAllowed {
|
||||||
|
log.Infof("enabling SSH server")
|
||||||
|
} else {
|
||||||
|
log.Infof("disabling SSH server")
|
||||||
|
}
|
||||||
|
config.ServerSSHAllowed = input.ServerSSHAllowed
|
||||||
|
updated = true
|
||||||
|
} else if config.ServerSSHAllowed == nil {
|
||||||
|
// enables SSH for configs from old versions to preserve backwards compatibility
|
||||||
|
log.Infof("falling back to enabled SSH server for pre-existing configuration")
|
||||||
|
config.ServerSSHAllowed = util.True()
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseURL parses and validates a service URL
|
// parseURL parses and validates a service URL
|
||||||
func parseURL(serviceName, serviceURL string) (*url.URL, error) {
|
func parseURL(serviceName, serviceURL string) (*url.URL, error) {
|
||||||
parsedMgmtURL, err := url.ParseRequestURI(serviceURL)
|
parsedMgmtURL, err := url.ParseRequestURI(serviceURL)
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
@@ -29,29 +31,45 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunClient with main logic.
|
type ConnectClient struct {
|
||||||
func RunClient(ctx context.Context, config *Config, statusRecorder *peer.Status) error {
|
ctx context.Context
|
||||||
return runClient(ctx, config, statusRecorder, MobileDependency{}, nil, nil, nil, nil)
|
config *Config
|
||||||
|
statusRecorder *peer.Status
|
||||||
|
engine *Engine
|
||||||
|
engineMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunClientWithProbes runs the client's main logic with probes attached
|
func NewConnectClient(
|
||||||
func RunClientWithProbes(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *Config,
|
config *Config,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
|
|
||||||
|
) *ConnectClient {
|
||||||
|
return &ConnectClient{
|
||||||
|
ctx: ctx,
|
||||||
|
config: config,
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
|
engineMutex: sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run with main logic.
|
||||||
|
func (c *ConnectClient) Run() error {
|
||||||
|
return c.run(MobileDependency{}, nil, nil, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithProbes runs the client's main logic with probes attached
|
||||||
|
func (c *ConnectClient) RunWithProbes(
|
||||||
mgmProbe *Probe,
|
mgmProbe *Probe,
|
||||||
signalProbe *Probe,
|
signalProbe *Probe,
|
||||||
relayProbe *Probe,
|
relayProbe *Probe,
|
||||||
wgProbe *Probe,
|
wgProbe *Probe,
|
||||||
) error {
|
) error {
|
||||||
return runClient(ctx, config, statusRecorder, MobileDependency{}, mgmProbe, signalProbe, relayProbe, wgProbe)
|
return c.run(MobileDependency{}, mgmProbe, signalProbe, relayProbe, wgProbe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunClientMobile with main logic on mobile system
|
// RunOnAndroid with main logic on mobile system
|
||||||
func RunClientMobile(
|
func (c *ConnectClient) RunOnAndroid(
|
||||||
ctx context.Context,
|
|
||||||
config *Config,
|
|
||||||
statusRecorder *peer.Status,
|
|
||||||
tunAdapter iface.TunAdapter,
|
tunAdapter iface.TunAdapter,
|
||||||
iFaceDiscover stdnet.ExternalIFaceDiscover,
|
iFaceDiscover stdnet.ExternalIFaceDiscover,
|
||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
@@ -66,29 +84,26 @@ func RunClientMobile(
|
|||||||
HostDNSAddresses: dnsAddresses,
|
HostDNSAddresses: dnsAddresses,
|
||||||
DnsReadyListener: dnsReadyListener,
|
DnsReadyListener: dnsReadyListener,
|
||||||
}
|
}
|
||||||
return runClient(ctx, config, statusRecorder, mobileDependency, nil, nil, nil, nil)
|
return c.run(mobileDependency, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunClientiOS(
|
func (c *ConnectClient) RunOniOS(
|
||||||
ctx context.Context,
|
|
||||||
config *Config,
|
|
||||||
statusRecorder *peer.Status,
|
|
||||||
fileDescriptor int32,
|
fileDescriptor int32,
|
||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
dnsManager dns.IosDnsManager,
|
dnsManager dns.IosDnsManager,
|
||||||
) error {
|
) error {
|
||||||
|
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
||||||
|
debug.SetGCPercent(5)
|
||||||
|
|
||||||
mobileDependency := MobileDependency{
|
mobileDependency := MobileDependency{
|
||||||
FileDescriptor: fileDescriptor,
|
FileDescriptor: fileDescriptor,
|
||||||
NetworkChangeListener: networkChangeListener,
|
NetworkChangeListener: networkChangeListener,
|
||||||
DnsManager: dnsManager,
|
DnsManager: dnsManager,
|
||||||
}
|
}
|
||||||
return runClient(ctx, config, statusRecorder, mobileDependency, nil, nil, nil, nil)
|
return c.run(mobileDependency, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClient(
|
func (c *ConnectClient) run(
|
||||||
ctx context.Context,
|
|
||||||
config *Config,
|
|
||||||
statusRecorder *peer.Status,
|
|
||||||
mobileDependency MobileDependency,
|
mobileDependency MobileDependency,
|
||||||
mgmProbe *Probe,
|
mgmProbe *Probe,
|
||||||
signalProbe *Probe,
|
signalProbe *Probe,
|
||||||
@@ -105,7 +120,7 @@ func runClient(
|
|||||||
|
|
||||||
// Check if client was not shut down in a clean way and restore DNS config if required.
|
// Check if client was not shut down in a clean way and restore DNS config if required.
|
||||||
// Otherwise, we might not be able to connect to the management server to retrieve new config.
|
// Otherwise, we might not be able to connect to the management server to retrieve new config.
|
||||||
if err := dns.CheckUncleanShutdown(config.WgIface); err != nil {
|
if err := dns.CheckUncleanShutdown(c.config.WgIface); err != nil {
|
||||||
log.Errorf("checking unclean shutdown error: %s", err)
|
log.Errorf("checking unclean shutdown error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +134,7 @@ func runClient(
|
|||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
state := CtxGetState(ctx)
|
state := CtxGetState(c.ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
s, err := state.Status()
|
s, err := state.Status()
|
||||||
if err != nil || s != StatusNeedsLogin {
|
if err != nil || s != StatusNeedsLogin {
|
||||||
@@ -128,49 +143,49 @@ func runClient(
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
wrapErr := state.Wrap
|
wrapErr := state.Wrap
|
||||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
myPrivateKey, err := wgtypes.ParseKey(c.config.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
log.Errorf("failed parsing Wireguard key %s: [%s]", c.config.PrivateKey, err.Error())
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var mgmTlsEnabled bool
|
var mgmTlsEnabled bool
|
||||||
if config.ManagementURL.Scheme == "https" {
|
if c.config.ManagementURL.Scheme == "https" {
|
||||||
mgmTlsEnabled = true
|
mgmTlsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
publicSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey))
|
publicSSHKey, err := ssh.GeneratePublicKey([]byte(c.config.SSHKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer statusRecorder.ClientStop()
|
defer c.statusRecorder.ClientStop()
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
// if context cancelled we not start new backoff cycle
|
// if context cancelled we not start new backoff cycle
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-c.ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Set(StatusConnecting)
|
state.Set(StatusConnecting)
|
||||||
|
|
||||||
engineCtx, cancel := context.WithCancel(ctx)
|
engineCtx, cancel := context.WithCancel(c.ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
statusRecorder.MarkManagementDisconnected(state.err)
|
c.statusRecorder.MarkManagementDisconnected(state.err)
|
||||||
statusRecorder.CleanLocalPeerState()
|
c.statusRecorder.CleanLocalPeerState()
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Debugf("connecting to the Management service %s", config.ManagementURL.Host)
|
log.Debugf("connecting to the Management service %s", c.config.ManagementURL.Host)
|
||||||
mgmClient, err := mgm.NewClient(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
mgmClient, err := mgm.NewClient(engineCtx, c.config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapErr(gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err))
|
return wrapErr(gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err))
|
||||||
}
|
}
|
||||||
mgmNotifier := statusRecorderToMgmConnStateNotifier(statusRecorder)
|
mgmNotifier := statusRecorderToMgmConnStateNotifier(c.statusRecorder)
|
||||||
mgmClient.SetConnStateListener(mgmNotifier)
|
mgmClient.SetConnStateListener(mgmNotifier)
|
||||||
|
|
||||||
log.Debugf("connected to the Management service %s", config.ManagementURL.Host)
|
log.Debugf("connected to the Management service %s", c.config.ManagementURL.Host)
|
||||||
defer func() {
|
defer func() {
|
||||||
err = mgmClient.Close()
|
err = mgmClient.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -188,7 +203,7 @@ func runClient(
|
|||||||
}
|
}
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
statusRecorder.MarkManagementConnected()
|
c.statusRecorder.MarkManagementConnected()
|
||||||
|
|
||||||
localPeerState := peer.LocalPeerState{
|
localPeerState := peer.LocalPeerState{
|
||||||
IP: loginResp.GetPeerConfig().GetAddress(),
|
IP: loginResp.GetPeerConfig().GetAddress(),
|
||||||
@@ -197,18 +212,18 @@ func runClient(
|
|||||||
FQDN: loginResp.GetPeerConfig().GetFqdn(),
|
FQDN: loginResp.GetPeerConfig().GetFqdn(),
|
||||||
}
|
}
|
||||||
|
|
||||||
statusRecorder.UpdateLocalPeerState(localPeerState)
|
c.statusRecorder.UpdateLocalPeerState(localPeerState)
|
||||||
|
|
||||||
signalURL := fmt.Sprintf("%s://%s",
|
signalURL := fmt.Sprintf("%s://%s",
|
||||||
strings.ToLower(loginResp.GetWiretrusteeConfig().GetSignal().GetProtocol().String()),
|
strings.ToLower(loginResp.GetWiretrusteeConfig().GetSignal().GetProtocol().String()),
|
||||||
loginResp.GetWiretrusteeConfig().GetSignal().GetUri(),
|
loginResp.GetWiretrusteeConfig().GetSignal().GetUri(),
|
||||||
)
|
)
|
||||||
|
|
||||||
statusRecorder.UpdateSignalAddress(signalURL)
|
c.statusRecorder.UpdateSignalAddress(signalURL)
|
||||||
|
|
||||||
statusRecorder.MarkSignalDisconnected(nil)
|
c.statusRecorder.MarkSignalDisconnected(nil)
|
||||||
defer func() {
|
defer func() {
|
||||||
statusRecorder.MarkSignalDisconnected(state.err)
|
c.statusRecorder.MarkSignalDisconnected(state.err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||||
@@ -224,35 +239,38 @@ func runClient(
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
signalNotifier := statusRecorderToSignalConnStateNotifier(statusRecorder)
|
signalNotifier := statusRecorderToSignalConnStateNotifier(c.statusRecorder)
|
||||||
signalClient.SetConnStateListener(signalNotifier)
|
signalClient.SetConnStateListener(signalNotifier)
|
||||||
|
|
||||||
statusRecorder.MarkSignalConnected()
|
c.statusRecorder.MarkSignalConnected()
|
||||||
|
|
||||||
peerConfig := loginResp.GetPeerConfig()
|
peerConfig := loginResp.GetPeerConfig()
|
||||||
|
|
||||||
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
engine := NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
|
c.engineMutex.Lock()
|
||||||
err = engine.Start()
|
c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, c.statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
|
||||||
|
c.engineMutex.Unlock()
|
||||||
|
|
||||||
|
err = c.engine.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while starting Netbird Connection Engine: %s", err)
|
log.Errorf("error while starting Netbird Connection Engine: %s", err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Netbird engine started, my IP is: ", peerConfig.Address)
|
log.Infof("Netbird engine started, the IP is: %s", peerConfig.GetAddress())
|
||||||
state.Set(StatusConnected)
|
state.Set(StatusConnected)
|
||||||
|
|
||||||
<-engineCtx.Done()
|
<-engineCtx.Done()
|
||||||
statusRecorder.ClientTeardown()
|
c.statusRecorder.ClientTeardown()
|
||||||
|
|
||||||
backOff.Reset()
|
backOff.Reset()
|
||||||
|
|
||||||
err = engine.Stop()
|
err = c.engine.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed stopping engine %v", err)
|
log.Errorf("failed stopping engine %v", err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
@@ -267,7 +285,7 @@ func runClient(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
statusRecorder.ClientStart()
|
c.statusRecorder.ClientStart()
|
||||||
err = backoff.Retry(operation, backOff)
|
err = backoff.Retry(operation, backOff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
|
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||||
@@ -279,6 +297,14 @@ func runClient(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ConnectClient) Engine() *Engine {
|
||||||
|
var e *Engine
|
||||||
|
c.engineMutex.Lock()
|
||||||
|
e = c.engine
|
||||||
|
c.engineMutex.Unlock()
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||||
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
||||||
engineConf := &EngineConfig{
|
engineConf := &EngineConfig{
|
||||||
@@ -288,6 +314,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
|||||||
DisableIPv6Discovery: config.DisableIPv6Discovery,
|
DisableIPv6Discovery: config.DisableIPv6Discovery,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: config.WgPort,
|
WgPort: config.WgPort,
|
||||||
|
NetworkMonitor: config.NetworkMonitor,
|
||||||
SSHKey: []byte(config.SSHKey),
|
SSHKey: []byte(config.SSHKey),
|
||||||
NATExternalIPs: config.NATExternalIPs,
|
NATExternalIPs: config.NATExternalIPs,
|
||||||
CustomDNSAddress: config.CustomDNSAddress,
|
CustomDNSAddress: config.CustomDNSAddress,
|
||||||
@@ -304,6 +331,15 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
|||||||
engineConf.PreSharedKey = &preSharedKey
|
engineConf.PreSharedKey = &preSharedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port, err := freePort(config.WgPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if port != config.WgPort {
|
||||||
|
log.Infof("using %d as wireguard port: %d is in use", port, config.WgPort)
|
||||||
|
}
|
||||||
|
engineConf.WgPort = port
|
||||||
|
|
||||||
return engineConf, nil
|
return engineConf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,3 +389,20 @@ func statusRecorderToSignalConnStateNotifier(statusRecorder *peer.Status) signal
|
|||||||
notifier, _ := sri.(signal.ConnStateNotifier)
|
notifier, _ := sri.(signal.ConnStateNotifier)
|
||||||
return notifier
|
return notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func freePort(start int) (int, error) {
|
||||||
|
addr := net.UDPAddr{}
|
||||||
|
if start == 0 {
|
||||||
|
start = iface.DefaultWgPort
|
||||||
|
}
|
||||||
|
for x := start; x <= 65535; x++ {
|
||||||
|
addr.Port = x
|
||||||
|
conn, err := net.ListenUDP("udp", &addr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("no free ports")
|
||||||
|
}
|
||||||
|
|||||||
57
client/internal/connect_test.go
Normal file
57
client/internal/connect_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_freePort(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
port int
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "available",
|
||||||
|
port: 51820,
|
||||||
|
want: 51820,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notavailable",
|
||||||
|
port: 51830,
|
||||||
|
want: 51831,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noports",
|
||||||
|
port: 65535,
|
||||||
|
want: 0,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
|
||||||
|
c1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 51830})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("freePort error = %v", err)
|
||||||
|
}
|
||||||
|
c2, err := net.ListenUDP("udp", &net.UDPAddr{Port: 65535})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("freePort error = %v", err)
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := freePort(tt.port)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("freePort() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("freePort() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
c1.Close()
|
||||||
|
c2.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,24 +47,20 @@ func (f *fileConfigurator) supportCustomPort() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
backupFileExist := false
|
backupFileExist := f.isBackupFileExist()
|
||||||
_, err := os.Stat(fileDefaultResolvConfBackupLocation)
|
|
||||||
if err == nil {
|
|
||||||
backupFileExist = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.RouteAll {
|
if !config.RouteAll {
|
||||||
if backupFileExist {
|
if backupFileExist {
|
||||||
err = f.restore()
|
f.repair.stopWatchFileChanges()
|
||||||
|
err := f.restore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group. Restoring the original file return err: %w", err)
|
return fmt.Errorf("restoring the original resolv.conf file return err: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
|
return fmt.Errorf("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !backupFileExist {
|
if !backupFileExist {
|
||||||
err = f.backup()
|
err := f.backup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to backup the resolv.conf file: %w", err)
|
return fmt.Errorf("unable to backup the resolv.conf file: %w", err)
|
||||||
}
|
}
|
||||||
@@ -184,6 +180,11 @@ func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Add
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fileConfigurator) isBackupFileExist() bool {
|
||||||
|
_, err := os.Stat(fileDefaultResolvConfBackupLocation)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func restoreResolvConfFile() error {
|
func restoreResolvConfFile() error {
|
||||||
log.Debugf("restoring unclean shutdown: restoring %s from %s", defaultResolvConfPath, fileUncleanShutdownResolvConfLocation)
|
log.Debugf("restoring unclean shutdown: restoring %s from %s", defaultResolvConfPath, fileUncleanShutdownResolvConfLocation)
|
||||||
|
|
||||||
|
|||||||
63
client/internal/dns/hosts_dns_holder.go
Normal file
63
client/internal/dns/hosts_dns_holder.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hostsDNSHolder struct {
|
||||||
|
unprotectedDNSList map[string]struct{}
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostsDNSHolder() *hostsDNSHolder {
|
||||||
|
return &hostsDNSHolder{
|
||||||
|
unprotectedDNSList: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hostsDNSHolder) set(list []string) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
h.unprotectedDNSList = make(map[string]struct{})
|
||||||
|
for _, dns := range list {
|
||||||
|
dnsAddr, err := h.normalizeAddress(dns)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h.unprotectedDNSList[dnsAddr] = struct{}{}
|
||||||
|
}
|
||||||
|
h.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hostsDNSHolder) get() map[string]struct{} {
|
||||||
|
h.mutex.RLock()
|
||||||
|
l := h.unprotectedDNSList
|
||||||
|
h.mutex.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:unused
|
||||||
|
func (h *hostsDNSHolder) isContain(upstream string) bool {
|
||||||
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
|
_, ok := h.unprotectedDNSList[upstream]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hostsDNSHolder) normalizeAddress(addr string) (string, error) {
|
||||||
|
a, err := netip.ParseAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("invalid upstream IP address: %s, error: %s", addr, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Is4() {
|
||||||
|
return fmt.Sprintf("%s:53", addr), nil
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("[%s]:53", addr), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,8 @@ func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
response := d.lookupRecord(r)
|
response := d.lookupRecord(r)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
replyMessage.Answer = append(replyMessage.Answer, response)
|
replyMessage.Answer = append(replyMessage.Answer, response)
|
||||||
|
} else {
|
||||||
|
replyMessage.Rcode = dns.RcodeNameError
|
||||||
}
|
}
|
||||||
|
|
||||||
err := w.WriteMsg(replyMessage)
|
err := w.WriteMsg(replyMessage)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -54,9 +55,8 @@ type DefaultServer struct {
|
|||||||
currentConfig HostDNSConfig
|
currentConfig HostDNSConfig
|
||||||
|
|
||||||
// permanent related properties
|
// permanent related properties
|
||||||
permanent bool
|
permanent bool
|
||||||
hostsDnsList []string
|
hostsDNSHolder *hostsDNSHolder
|
||||||
hostsDnsListLock sync.Mutex
|
|
||||||
|
|
||||||
// make sense on mobile only
|
// make sense on mobile only
|
||||||
searchDomainNotifier *notifier
|
searchDomainNotifier *notifier
|
||||||
@@ -113,8 +113,8 @@ func NewDefaultServerPermanentUpstream(
|
|||||||
) *DefaultServer {
|
) *DefaultServer {
|
||||||
log.Debugf("host dns address list is: %v", hostsDnsList)
|
log.Debugf("host dns address list is: %v", hostsDnsList)
|
||||||
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
|
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
|
||||||
|
ds.hostsDNSHolder.set(hostsDnsList)
|
||||||
ds.permanent = true
|
ds.permanent = true
|
||||||
ds.hostsDnsList = hostsDnsList
|
|
||||||
ds.addHostRootZone()
|
ds.addHostRootZone()
|
||||||
ds.currentConfig = dnsConfigToHostDNSConfig(config, ds.service.RuntimeIP(), ds.service.RuntimePort())
|
ds.currentConfig = dnsConfigToHostDNSConfig(config, ds.service.RuntimeIP(), ds.service.RuntimePort())
|
||||||
ds.searchDomainNotifier = newNotifier(ds.SearchDomains())
|
ds.searchDomainNotifier = newNotifier(ds.SearchDomains())
|
||||||
@@ -147,6 +147,7 @@ func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService servi
|
|||||||
},
|
},
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
|
hostsDNSHolder: newHostsDNSHolder(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultServer
|
return defaultServer
|
||||||
@@ -202,10 +203,8 @@ func (s *DefaultServer) Stop() {
|
|||||||
// OnUpdatedHostDNSServer update the DNS servers addresses for root zones
|
// OnUpdatedHostDNSServer update the DNS servers addresses for root zones
|
||||||
// It will be applied if the mgm server do not enforce DNS settings for root zone
|
// It will be applied if the mgm server do not enforce DNS settings for root zone
|
||||||
func (s *DefaultServer) OnUpdatedHostDNSServer(hostsDnsList []string) {
|
func (s *DefaultServer) OnUpdatedHostDNSServer(hostsDnsList []string) {
|
||||||
s.hostsDnsListLock.Lock()
|
s.hostsDNSHolder.set(hostsDnsList)
|
||||||
defer s.hostsDnsListLock.Unlock()
|
|
||||||
|
|
||||||
s.hostsDnsList = hostsDnsList
|
|
||||||
_, ok := s.dnsMuxMap[nbdns.RootZone]
|
_, ok := s.dnsMuxMap[nbdns.RootZone]
|
||||||
if ok {
|
if ok {
|
||||||
log.Debugf("on new host DNS config but skip to apply it")
|
log.Debugf("on new host DNS config but skip to apply it")
|
||||||
@@ -374,6 +373,7 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam
|
|||||||
s.wgInterface.Address().IP,
|
s.wgInterface.Address().IP,
|
||||||
s.wgInterface.Address().Network,
|
s.wgInterface.Address().Network,
|
||||||
s.statusRecorder,
|
s.statusRecorder,
|
||||||
|
s.hostsDNSHolder,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create a new upstream resolver, error: %v", err)
|
return nil, fmt.Errorf("unable to create a new upstream resolver, error: %v", err)
|
||||||
@@ -452,9 +452,7 @@ func (s *DefaultServer) updateMux(muxUpdates []muxUpdate) {
|
|||||||
_, found := muxUpdateMap[key]
|
_, found := muxUpdateMap[key]
|
||||||
if !found {
|
if !found {
|
||||||
if !isContainRootUpdate && key == nbdns.RootZone {
|
if !isContainRootUpdate && key == nbdns.RootZone {
|
||||||
s.hostsDnsListLock.Lock()
|
|
||||||
s.addHostRootZone()
|
s.addHostRootZone()
|
||||||
s.hostsDnsListLock.Unlock()
|
|
||||||
existingHandler.stop()
|
existingHandler.stop()
|
||||||
} else {
|
} else {
|
||||||
existingHandler.stop()
|
existingHandler.stop()
|
||||||
@@ -512,6 +510,7 @@ func (s *DefaultServer) upstreamCallbacks(
|
|||||||
if nsGroup.Primary {
|
if nsGroup.Primary {
|
||||||
removeIndex[nbdns.RootZone] = -1
|
removeIndex[nbdns.RootZone] = -1
|
||||||
s.currentConfig.RouteAll = false
|
s.currentConfig.RouteAll = false
|
||||||
|
s.service.DeregisterMux(nbdns.RootZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, item := range s.currentConfig.Domains {
|
for i, item := range s.currentConfig.Domains {
|
||||||
@@ -521,10 +520,15 @@ func (s *DefaultServer) upstreamCallbacks(
|
|||||||
removeIndex[item.Domain] = i
|
removeIndex[item.Domain] = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
||||||
l.Errorf("Failed to apply nameserver deactivation on the host: %v", err)
|
l.Errorf("Failed to apply nameserver deactivation on the host: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "android" && nsGroup.Primary && len(s.hostsDNSHolder.get()) > 0 {
|
||||||
|
s.addHostRootZone()
|
||||||
|
}
|
||||||
|
|
||||||
s.updateNSState(nsGroup, err, false)
|
s.updateNSState(nsGroup, err, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -545,6 +549,7 @@ func (s *DefaultServer) upstreamCallbacks(
|
|||||||
|
|
||||||
if nsGroup.Primary {
|
if nsGroup.Primary {
|
||||||
s.currentConfig.RouteAll = true
|
s.currentConfig.RouteAll = true
|
||||||
|
s.service.RegisterMux(nbdns.RootZone, handler)
|
||||||
}
|
}
|
||||||
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
||||||
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
|
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
|
||||||
@@ -562,25 +567,16 @@ func (s *DefaultServer) addHostRootZone() {
|
|||||||
s.wgInterface.Address().IP,
|
s.wgInterface.Address().IP,
|
||||||
s.wgInterface.Address().Network,
|
s.wgInterface.Address().Network,
|
||||||
s.statusRecorder,
|
s.statusRecorder,
|
||||||
|
s.hostsDNSHolder,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to create a new upstream resolver, error: %v", err)
|
log.Errorf("unable to create a new upstream resolver, error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handler.upstreamServers = make([]string, len(s.hostsDnsList))
|
|
||||||
for n, ua := range s.hostsDnsList {
|
|
||||||
a, err := netip.ParseAddr(ua)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("invalid upstream IP address: %s, error: %s", ua, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ipString := ua
|
handler.upstreamServers = make([]string, 0)
|
||||||
if !a.Is4() {
|
for k := range s.hostsDNSHolder.get() {
|
||||||
ipString = fmt.Sprintf("[%s]", ua)
|
handler.upstreamServers = append(handler.upstreamServers, k)
|
||||||
}
|
|
||||||
|
|
||||||
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString)
|
|
||||||
}
|
}
|
||||||
handler.deactivate = func(error) {}
|
handler.deactivate = func(error) {}
|
||||||
handler.reactivate = func() {}
|
handler.reactivate = func() {}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -260,13 +259,10 @@ func (u *upstreamResolverBase) disable(err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo test the deactivation logic, it seems to affect the client
|
log.Warnf("Upstream resolving is Disabled for %v", reactivatePeriod)
|
||||||
if runtime.GOOS != "ios" {
|
u.deactivate(err)
|
||||||
log.Warnf("Upstream resolving is Disabled for %v", reactivatePeriod)
|
u.disabled = true
|
||||||
u.deactivate(err)
|
go u.waitUntilResponse()
|
||||||
u.disabled = true
|
|
||||||
go u.waitUntilResponse()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *upstreamResolverBase) testNameserver(server string) error {
|
func (u *upstreamResolverBase) testNameserver(server string) error {
|
||||||
|
|||||||
84
client/internal/dns/upstream_android.go
Normal file
84
client/internal/dns/upstream_android.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type upstreamResolver struct {
|
||||||
|
*upstreamResolverBase
|
||||||
|
hostsDNSHolder *hostsDNSHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUpstreamResolver in Android we need to distinguish the DNS servers to available through VPN or outside of VPN
|
||||||
|
// In case if the assigned DNS address is available only in the protected network then the resolver will time out at the
|
||||||
|
// first time, and we need to wait for a while to start to use again the proper DNS resolver.
|
||||||
|
func newUpstreamResolver(
|
||||||
|
ctx context.Context,
|
||||||
|
_ string,
|
||||||
|
_ net.IP,
|
||||||
|
_ *net.IPNet,
|
||||||
|
statusRecorder *peer.Status,
|
||||||
|
hostsDNSHolder *hostsDNSHolder,
|
||||||
|
) (*upstreamResolver, error) {
|
||||||
|
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
||||||
|
c := &upstreamResolver{
|
||||||
|
upstreamResolverBase: upstreamResolverBase,
|
||||||
|
hostsDNSHolder: hostsDNSHolder,
|
||||||
|
}
|
||||||
|
upstreamResolverBase.upstreamClient = c
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// exchange in case of Android if the upstream is a local resolver then we do not need to mark the socket as protected.
|
||||||
|
// In other case the DNS resolvation goes through the VPN, so we need to force to use the
|
||||||
|
func (u *upstreamResolver) exchange(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||||
|
if u.isLocalResolver(upstream) {
|
||||||
|
return u.exchangeWithoutVPN(ctx, upstream, r)
|
||||||
|
} else {
|
||||||
|
return u.exchangeWithinVPN(ctx, upstream, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *upstreamResolver) exchangeWithinVPN(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||||
|
upstreamExchangeClient := &dns.Client{}
|
||||||
|
return upstreamExchangeClient.ExchangeContext(ctx, r, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exchangeWithoutVPN protect the UDP socket by Android SDK to avoid to goes through the VPN
|
||||||
|
func (u *upstreamResolver) exchangeWithoutVPN(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||||
|
timeout := upstreamTimeout
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
timeout = time.Until(deadline)
|
||||||
|
}
|
||||||
|
dialTimeout := timeout
|
||||||
|
|
||||||
|
nbDialer := nbnet.NewDialer()
|
||||||
|
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
return nbDialer.Control(network, address, c)
|
||||||
|
},
|
||||||
|
Timeout: dialTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamExchangeClient := &dns.Client{
|
||||||
|
Dialer: dialer,
|
||||||
|
}
|
||||||
|
|
||||||
|
return upstreamExchangeClient.Exchange(r, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *upstreamResolver) isLocalResolver(upstream string) bool {
|
||||||
|
if u.hostsDNSHolder.isContain(upstream) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !ios
|
//go:build !android && !ios
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type upstreamResolverNonIOS struct {
|
type upstreamResolver struct {
|
||||||
*upstreamResolverBase
|
*upstreamResolverBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,16 +22,17 @@ func newUpstreamResolver(
|
|||||||
_ net.IP,
|
_ net.IP,
|
||||||
_ *net.IPNet,
|
_ *net.IPNet,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
) (*upstreamResolverNonIOS, error) {
|
_ *hostsDNSHolder,
|
||||||
|
) (*upstreamResolver, error) {
|
||||||
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
||||||
nonIOS := &upstreamResolverNonIOS{
|
nonIOS := &upstreamResolver{
|
||||||
upstreamResolverBase: upstreamResolverBase,
|
upstreamResolverBase: upstreamResolverBase,
|
||||||
}
|
}
|
||||||
upstreamResolverBase.upstreamClient = nonIOS
|
upstreamResolverBase.upstreamClient = nonIOS
|
||||||
return nonIOS, nil
|
return nonIOS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *upstreamResolverNonIOS) exchange(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
func (u *upstreamResolver) exchange(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||||
upstreamExchangeClient := &dns.Client{}
|
upstreamExchangeClient := &dns.Client{}
|
||||||
return upstreamExchangeClient.ExchangeContext(ctx, r, upstream)
|
return upstreamExchangeClient.ExchangeContext(ctx, r, upstream)
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ func newUpstreamResolver(
|
|||||||
ip net.IP,
|
ip net.IP,
|
||||||
net *net.IPNet,
|
net *net.IPNet,
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
|
_ *hostsDNSHolder,
|
||||||
) (*upstreamResolverIOS, error) {
|
) (*upstreamResolverIOS, error) {
|
||||||
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func TestUpstreamResolver_ServeDNS(t *testing.T) {
|
|||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
resolver, _ := newUpstreamResolver(ctx, "", net.IP{}, &net.IPNet{}, nil)
|
resolver, _ := newUpstreamResolver(ctx, "", net.IP{}, &net.IPNet{}, nil, nil)
|
||||||
resolver.upstreamServers = testCase.InputServers
|
resolver.upstreamServers = testCase.InputServers
|
||||||
resolver.upstreamTimeout = testCase.timeout
|
resolver.upstreamTimeout = testCase.timeout
|
||||||
if testCase.cancelCTX {
|
if testCase.cancelCTX {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
@@ -21,6 +22,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/firewall/manager"
|
"github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/internal/acl"
|
"github.com/netbirdio/netbird/client/internal/acl"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/networkmonitor"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/relay"
|
"github.com/netbirdio/netbird/client/internal/relay"
|
||||||
"github.com/netbirdio/netbird/client/internal/rosenpass"
|
"github.com/netbirdio/netbird/client/internal/rosenpass"
|
||||||
@@ -60,6 +62,9 @@ type EngineConfig struct {
|
|||||||
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
|
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
|
||||||
WgPrivateKey wgtypes.Key
|
WgPrivateKey wgtypes.Key
|
||||||
|
|
||||||
|
// NetworkMonitor is a flag to enable network monitoring
|
||||||
|
NetworkMonitor bool
|
||||||
|
|
||||||
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
|
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
DisableIPv6Discovery bool
|
DisableIPv6Discovery bool
|
||||||
@@ -111,9 +116,14 @@ type Engine struct {
|
|||||||
// TURNs is a list of STUN servers used by ICE
|
// TURNs is a list of STUN servers used by ICE
|
||||||
TURNs []*stun.URI
|
TURNs []*stun.URI
|
||||||
|
|
||||||
cancel context.CancelFunc
|
// clientRoutes is the most recent list of clientRoutes received from the Management Service
|
||||||
|
clientRoutes route.HAMap
|
||||||
|
|
||||||
ctx context.Context
|
clientCtx context.Context
|
||||||
|
clientCancel context.CancelFunc
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
wgProxyFactory *wgproxy.Factory
|
wgProxyFactory *wgproxy.Factory
|
||||||
@@ -123,6 +133,8 @@ type Engine struct {
|
|||||||
// networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service
|
// networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service
|
||||||
networkSerial uint64
|
networkSerial uint64
|
||||||
|
|
||||||
|
networkMonitor *networkmonitor.NetworkMonitor
|
||||||
|
|
||||||
sshServerFunc func(hostKeyPEM []byte, addr string) (nbssh.Server, error)
|
sshServerFunc func(hostKeyPEM []byte, addr string) (nbssh.Server, error)
|
||||||
sshServer nbssh.Server
|
sshServer nbssh.Server
|
||||||
|
|
||||||
@@ -138,6 +150,8 @@ type Engine struct {
|
|||||||
signalProbe *Probe
|
signalProbe *Probe
|
||||||
relayProbe *Probe
|
relayProbe *Probe
|
||||||
wgProbe *Probe
|
wgProbe *Probe
|
||||||
|
|
||||||
|
wgConnWorker sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -148,8 +162,8 @@ type Peer struct {
|
|||||||
|
|
||||||
// NewEngine creates a new Connection Engine
|
// NewEngine creates a new Connection Engine
|
||||||
func NewEngine(
|
func NewEngine(
|
||||||
ctx context.Context,
|
clientCtx context.Context,
|
||||||
cancel context.CancelFunc,
|
clientCancel context.CancelFunc,
|
||||||
signalClient signal.Client,
|
signalClient signal.Client,
|
||||||
mgmClient mgm.Client,
|
mgmClient mgm.Client,
|
||||||
config *EngineConfig,
|
config *EngineConfig,
|
||||||
@@ -157,8 +171,8 @@ func NewEngine(
|
|||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
) *Engine {
|
) *Engine {
|
||||||
return NewEngineWithProbes(
|
return NewEngineWithProbes(
|
||||||
ctx,
|
clientCtx,
|
||||||
cancel,
|
clientCancel,
|
||||||
signalClient,
|
signalClient,
|
||||||
mgmClient,
|
mgmClient,
|
||||||
config,
|
config,
|
||||||
@@ -173,8 +187,8 @@ func NewEngine(
|
|||||||
|
|
||||||
// NewEngineWithProbes creates a new Connection Engine with probes attached
|
// NewEngineWithProbes creates a new Connection Engine with probes attached
|
||||||
func NewEngineWithProbes(
|
func NewEngineWithProbes(
|
||||||
ctx context.Context,
|
clientCtx context.Context,
|
||||||
cancel context.CancelFunc,
|
clientCancel context.CancelFunc,
|
||||||
signalClient signal.Client,
|
signalClient signal.Client,
|
||||||
mgmClient mgm.Client,
|
mgmClient mgm.Client,
|
||||||
config *EngineConfig,
|
config *EngineConfig,
|
||||||
@@ -185,9 +199,10 @@ func NewEngineWithProbes(
|
|||||||
relayProbe *Probe,
|
relayProbe *Probe,
|
||||||
wgProbe *Probe,
|
wgProbe *Probe,
|
||||||
) *Engine {
|
) *Engine {
|
||||||
|
|
||||||
return &Engine{
|
return &Engine{
|
||||||
ctx: ctx,
|
clientCtx: clientCtx,
|
||||||
cancel: cancel,
|
clientCancel: clientCancel,
|
||||||
signal: signalClient,
|
signal: signalClient,
|
||||||
mgmClient: mgmClient,
|
mgmClient: mgmClient,
|
||||||
peerConns: make(map[string]*peer.Conn),
|
peerConns: make(map[string]*peer.Conn),
|
||||||
@@ -199,7 +214,6 @@ func NewEngineWithProbes(
|
|||||||
networkSerial: 0,
|
networkSerial: 0,
|
||||||
sshServerFunc: nbssh.DefaultSSHServer,
|
sshServerFunc: nbssh.DefaultSSHServer,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
wgProxyFactory: wgproxy.NewFactory(config.WgPort),
|
|
||||||
mgmProbe: mgmProbe,
|
mgmProbe: mgmProbe,
|
||||||
signalProbe: signalProbe,
|
signalProbe: signalProbe,
|
||||||
relayProbe: relayProbe,
|
relayProbe: relayProbe,
|
||||||
@@ -211,16 +225,29 @@ func (e *Engine) Stop() error {
|
|||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
|
if e.cancel != nil {
|
||||||
|
e.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopping network monitor first to avoid starting the engine again
|
||||||
|
if e.networkMonitor != nil {
|
||||||
|
e.networkMonitor.Stop()
|
||||||
|
}
|
||||||
|
log.Info("Network monitor: stopped")
|
||||||
|
|
||||||
err := e.removeAllPeers()
|
err := e.removeAllPeers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.clientRoutes = nil
|
||||||
|
|
||||||
// very ugly but we want to remove peers from the WireGuard interface first before removing interface.
|
// very ugly but we want to remove peers from the WireGuard interface first before removing interface.
|
||||||
// Removing peers happens in the conn.CLose() asynchronously
|
// Removing peers happens in the conn.Close() asynchronously
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
e.close()
|
e.close()
|
||||||
|
e.wgConnWorker.Wait()
|
||||||
log.Infof("stopped Netbird Engine")
|
log.Infof("stopped Netbird Engine")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -232,6 +259,13 @@ func (e *Engine) Start() error {
|
|||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
|
if e.cancel != nil {
|
||||||
|
e.cancel()
|
||||||
|
}
|
||||||
|
e.ctx, e.cancel = context.WithCancel(e.clientCtx)
|
||||||
|
|
||||||
|
e.wgProxyFactory = wgproxy.NewFactory(e.ctx, e.config.WgPort)
|
||||||
|
|
||||||
wgIface, err := e.newWgIface()
|
wgIface, err := e.newWgIface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed creating wireguard interface instance %s: [%s]", e.config.WgIfaceName, err)
|
log.Errorf("failed creating wireguard interface instance %s: [%s]", e.config.WgIfaceName, err)
|
||||||
@@ -315,6 +349,9 @@ func (e *Engine) Start() error {
|
|||||||
e.receiveManagementEvents()
|
e.receiveManagementEvents()
|
||||||
e.receiveProbeEvents()
|
e.receiveProbeEvents()
|
||||||
|
|
||||||
|
// starting network monitor at the very last to avoid disruptions
|
||||||
|
e.startNetworkMonitor()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,12 +620,12 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
|||||||
// E.g. when a new peer has been registered and we are allowed to connect to it.
|
// E.g. when a new peer has been registered and we are allowed to connect to it.
|
||||||
func (e *Engine) receiveManagementEvents() {
|
func (e *Engine) receiveManagementEvents() {
|
||||||
go func() {
|
go func() {
|
||||||
err := e.mgmClient.Sync(e.handleSync)
|
err := e.mgmClient.Sync(e.ctx, e.handleSync)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// happens if management is unavailable for a long time.
|
// happens if management is unavailable for a long time.
|
||||||
// We want to cancel the operation of the whole client
|
// We want to cancel the operation of the whole client
|
||||||
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||||
e.cancel()
|
e.clientCancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debugf("stopped receiving updates from Management Service")
|
log.Debugf("stopped receiving updates from Management Service")
|
||||||
@@ -695,11 +732,14 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
if protoRoutes == nil {
|
if protoRoutes == nil {
|
||||||
protoRoutes = []*mgmProto.Route{}
|
protoRoutes = []*mgmProto.Route{}
|
||||||
}
|
}
|
||||||
err := e.routeManager.UpdateRoutes(serial, toRoutes(protoRoutes))
|
|
||||||
|
_, clientRoutes, err := e.routeManager.UpdateRoutes(serial, toRoutes(protoRoutes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to update routes, err: %v", err)
|
log.Errorf("failed to update clientRoutes, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.clientRoutes = clientRoutes
|
||||||
|
|
||||||
protoDNSConfig := networkMap.GetDNSConfig()
|
protoDNSConfig := networkMap.GetDNSConfig()
|
||||||
if protoDNSConfig == nil {
|
if protoDNSConfig == nil {
|
||||||
protoDNSConfig = &mgmProto.DNSConfig{}
|
protoDNSConfig = &mgmProto.DNSConfig{}
|
||||||
@@ -728,9 +768,9 @@ func toRoutes(protoRoutes []*mgmProto.Route) []*route.Route {
|
|||||||
for _, protoRoute := range protoRoutes {
|
for _, protoRoute := range protoRoutes {
|
||||||
_, prefix, _ := route.ParseNetwork(protoRoute.Network)
|
_, prefix, _ := route.ParseNetwork(protoRoute.Network)
|
||||||
convertedRoute := &route.Route{
|
convertedRoute := &route.Route{
|
||||||
ID: protoRoute.ID,
|
ID: route.ID(protoRoute.ID),
|
||||||
Network: prefix,
|
Network: prefix,
|
||||||
NetID: protoRoute.NetID,
|
NetID: route.NetID(protoRoute.NetID),
|
||||||
NetworkType: route.NetworkType(protoRoute.NetworkType),
|
NetworkType: route.NetworkType(protoRoute.NetworkType),
|
||||||
Peer: protoRoute.Peer,
|
Peer: protoRoute.Peer,
|
||||||
Metric: int(protoRoute.Metric),
|
Metric: int(protoRoute.Metric),
|
||||||
@@ -832,18 +872,25 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.wgConnWorker.Add(1)
|
||||||
go e.connWorker(conn, peerKey)
|
go e.connWorker(conn, peerKey)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
||||||
|
defer e.wgConnWorker.Done()
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// randomize starting time a bit
|
// randomize starting time a bit
|
||||||
min := 500
|
min := 500
|
||||||
max := 2000
|
max := 2000
|
||||||
time.Sleep(time.Duration(rand.Intn(max-min)+min) * time.Millisecond)
|
duration := time.Duration(rand.Intn(max-min)+min) * time.Millisecond
|
||||||
|
select {
|
||||||
|
case <-e.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(duration):
|
||||||
|
}
|
||||||
|
|
||||||
// if peer has been removed -> give up
|
// if peer has been removed -> give up
|
||||||
if !e.peerExists(peerKey) {
|
if !e.peerExists(peerKey) {
|
||||||
@@ -861,11 +908,12 @@ func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
|||||||
conn.UpdateStunTurn(append(e.STUNs, e.TURNs...))
|
conn.UpdateStunTurn(append(e.STUNs, e.TURNs...))
|
||||||
e.syncMsgMux.Unlock()
|
e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
err := conn.Open()
|
err := conn.Open(e.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("connection to peer %s failed: %v", peerKey, err)
|
log.Debugf("connection to peer %s failed: %v", peerKey, err)
|
||||||
switch err.(type) {
|
var connectionClosedError *peer.ConnectionClosedError
|
||||||
case *peer.ConnectionClosedError:
|
switch {
|
||||||
|
case errors.As(err, &connectionClosedError):
|
||||||
// conn has been forced to close, so we exit the loop
|
// conn has been forced to close, so we exit the loop
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
@@ -976,7 +1024,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
|
|||||||
func (e *Engine) receiveSignalEvents() {
|
func (e *Engine) receiveSignalEvents() {
|
||||||
go func() {
|
go func() {
|
||||||
// connect to a stream of messages coming from the signal server
|
// connect to a stream of messages coming from the signal server
|
||||||
err := e.signal.Receive(func(msg *sProto.Message) error {
|
err := e.signal.Receive(e.ctx, func(msg *sProto.Message) error {
|
||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
defer e.syncMsgMux.Unlock()
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
@@ -1050,7 +1098,7 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
// happens if signal is unavailable for a long time.
|
// happens if signal is unavailable for a long time.
|
||||||
// We want to cancel the operation of the whole client
|
// We want to cancel the operation of the whole client
|
||||||
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||||
e.cancel()
|
e.clientCancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -1111,13 +1159,16 @@ func (e *Engine) parseNATExternalIPMappings() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) close() {
|
func (e *Engine) close() {
|
||||||
if err := e.wgProxyFactory.Free(); err != nil {
|
if e.wgProxyFactory != nil {
|
||||||
log.Errorf("failed closing ebpf proxy: %s", err)
|
if err := e.wgProxyFactory.Free(); err != nil {
|
||||||
|
log.Errorf("failed closing ebpf proxy: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop/restore DNS first so dbus and friends don't complain because of a missing interface
|
// stop/restore DNS first so dbus and friends don't complain because of a missing interface
|
||||||
if e.dnsServer != nil {
|
if e.dnsServer != nil {
|
||||||
e.dnsServer.Stop()
|
e.dnsServer.Stop()
|
||||||
|
e.dnsServer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.routeManager != nil {
|
if e.routeManager != nil {
|
||||||
@@ -1229,6 +1280,25 @@ func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClientRoutes returns the current routes from the route map
|
||||||
|
func (e *Engine) GetClientRoutes() route.HAMap {
|
||||||
|
return e.clientRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientRoutesWithNetID returns the current routes from the route map, but the keys consist of the network ID only
|
||||||
|
func (e *Engine) GetClientRoutesWithNetID() map[route.NetID][]*route.Route {
|
||||||
|
routes := make(map[route.NetID][]*route.Route, len(e.clientRoutes))
|
||||||
|
for id, v := range e.clientRoutes {
|
||||||
|
routes[id.NetID()] = v
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRouteManager returns the route manager
|
||||||
|
func (e *Engine) GetRouteManager() routemanager.Manager {
|
||||||
|
return e.routeManager
|
||||||
|
}
|
||||||
|
|
||||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1329,3 +1399,26 @@ func (e *Engine) probeSTUNs() []relay.ProbeResult {
|
|||||||
func (e *Engine) probeTURNs() []relay.ProbeResult {
|
func (e *Engine) probeTURNs() []relay.ProbeResult {
|
||||||
return relay.ProbeAll(e.ctx, relay.ProbeTURN, e.TURNs)
|
return relay.ProbeAll(e.ctx, relay.ProbeTURN, e.TURNs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Engine) startNetworkMonitor() {
|
||||||
|
if !e.config.NetworkMonitor {
|
||||||
|
log.Infof("Network monitor is disabled, not starting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.networkMonitor = networkmonitor.New()
|
||||||
|
go func() {
|
||||||
|
err := e.networkMonitor.Start(e.ctx, func() {
|
||||||
|
log.Infof("Network monitor detected network change, restarting engine")
|
||||||
|
if err := e.Stop(); err != nil {
|
||||||
|
log.Errorf("Failed to stop engine: %v", err)
|
||||||
|
}
|
||||||
|
if err := e.Start(); err != nil {
|
||||||
|
log.Errorf("Failed to start engine: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil && !errors.Is(err, networkmonitor.ErrStopped) {
|
||||||
|
log.Errorf("Network monitor: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
@@ -228,6 +229,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
engine.udpMux = bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: conn})
|
engine.udpMux = bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: conn})
|
||||||
|
engine.ctx = ctx
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
@@ -391,7 +393,7 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
// feed updates to Engine via mocked Management client
|
// feed updates to Engine via mocked Management client
|
||||||
updates := make(chan *mgmtProto.SyncResponse)
|
updates := make(chan *mgmtProto.SyncResponse)
|
||||||
defer close(updates)
|
defer close(updates)
|
||||||
syncFunc := func(msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
syncFunc := func(ctx context.Context, msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
||||||
for msg := range updates {
|
for msg := range updates {
|
||||||
err := msgHandler(msg)
|
err := msgHandler(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -407,6 +409,7 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
||||||
|
engine.ctx = ctx
|
||||||
|
|
||||||
engine.dnsServer = &dns.MockServer{
|
engine.dnsServer = &dns.MockServer{
|
||||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||||
@@ -565,6 +568,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
||||||
|
engine.ctx = ctx
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -577,10 +581,10 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
}{}
|
}{}
|
||||||
|
|
||||||
mockRouteManager := &routemanager.MockManager{
|
mockRouteManager := &routemanager.MockManager{
|
||||||
UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) error {
|
UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap, error) {
|
||||||
input.inputSerial = updateSerial
|
input.inputSerial = updateSerial
|
||||||
input.inputRoutes = newRoutes
|
input.inputRoutes = newRoutes
|
||||||
return testCase.inputErr
|
return nil, nil, testCase.inputErr
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,8 +601,8 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
err = engine.updateNetworkMap(testCase.networkMap)
|
err = engine.updateNetworkMap(testCase.networkMap)
|
||||||
assert.NoError(t, err, "shouldn't return error")
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
assert.Equal(t, testCase.expectedSerial, input.inputSerial, "serial should match")
|
assert.Equal(t, testCase.expectedSerial, input.inputSerial, "serial should match")
|
||||||
assert.Len(t, input.inputRoutes, testCase.expectedLen, "routes len should match")
|
assert.Len(t, input.inputRoutes, testCase.expectedLen, "clientRoutes len should match")
|
||||||
assert.Equal(t, testCase.expectedRoutes, input.inputRoutes, "routes should match")
|
assert.Equal(t, testCase.expectedRoutes, input.inputRoutes, "clientRoutes should match")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,6 +738,8 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
||||||
|
engine.ctx = ctx
|
||||||
|
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -742,8 +748,8 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
assert.NoError(t, err, "shouldn't return error")
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
mockRouteManager := &routemanager.MockManager{
|
mockRouteManager := &routemanager.MockManager{
|
||||||
UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) error {
|
UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap, error) {
|
||||||
return nil
|
return nil, nil, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1002,7 +1008,9 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
|||||||
WgPort: wgPort,
|
WgPort: wgPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm")), nil
|
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm")), nil
|
||||||
|
e.ctx = ctx
|
||||||
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSignal() (*grpc.Server, string, error) {
|
func startSignal() (*grpc.Server, string, error) {
|
||||||
@@ -1041,7 +1049,7 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
store, err := server.NewStoreFromJson(config.Datadir, nil)
|
store, _, err := server.NewTestStoreFromJson(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey)
|
serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey)
|
||||||
if isRegistrationNeeded(err) {
|
if serverKey != nil && isRegistrationNeeded(err) {
|
||||||
log.Debugf("peer registration required")
|
log.Debugf("peer registration required")
|
||||||
_, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey)
|
_, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey)
|
||||||
return err
|
return err
|
||||||
|
|||||||
21
client/internal/networkmonitor/monitor.go
Normal file
21
client/internal/networkmonitor/monitor.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package networkmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrStopped = errors.New("monitor has been stopped")
|
||||||
|
|
||||||
|
// NetworkMonitor watches for changes in network configuration.
|
||||||
|
type NetworkMonitor struct {
|
||||||
|
cancel context.CancelFunc
|
||||||
|
wg sync.WaitGroup
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new network monitor.
|
||||||
|
func New() *NetworkMonitor {
|
||||||
|
return &NetworkMonitor{}
|
||||||
|
}
|
||||||
133
client/internal/networkmonitor/monitor_bsd.go
Normal file
133
client/internal/networkmonitor/monitor_bsd.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//go:build (darwin && !ios) || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
|
||||||
|
package networkmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/route"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkChange(ctx context.Context, nexthopv4 netip.Addr, intfv4 *net.Interface, nexthopv6 netip.Addr, intfv6 *net.Interface, callback func()) error {
|
||||||
|
fd, err := unix.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open routing socket: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := unix.Close(fd); err != nil {
|
||||||
|
log.Errorf("Network monitor: failed to close routing socket: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrStopped
|
||||||
|
default:
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
n, err := unix.Read(fd, buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Network monitor: failed to read from routing socket: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n < unix.SizeofRtMsghdr {
|
||||||
|
log.Errorf("Network monitor: read from routing socket returned less than expected: %d bytes", n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := (*unix.RtMsghdr)(unsafe.Pointer(&buf[0]))
|
||||||
|
|
||||||
|
switch msg.Type {
|
||||||
|
|
||||||
|
// handle interface state changes
|
||||||
|
case unix.RTM_IFINFO:
|
||||||
|
ifinfo, err := parseInterfaceMessage(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Network monitor: error parsing interface message: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if msg.Flags&unix.IFF_UP != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (intfv4 == nil || ifinfo.Index != intfv4.Index) && (intfv6 == nil || ifinfo.Index != intfv6.Index) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Network monitor: monitored interface (%s) is down.", ifinfo.Name)
|
||||||
|
go callback()
|
||||||
|
|
||||||
|
// handle route changes
|
||||||
|
case unix.RTM_ADD, syscall.RTM_DELETE:
|
||||||
|
route, err := parseRouteMessage(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Network monitor: error parsing routing message: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !route.Dst.Addr().IsUnspecified() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
intf := "<nil>"
|
||||||
|
if route.Interface != nil {
|
||||||
|
intf = route.Interface.Name
|
||||||
|
}
|
||||||
|
switch msg.Type {
|
||||||
|
case unix.RTM_ADD:
|
||||||
|
log.Infof("Network monitor: default route changed: via %s, interface %s", route.Gw, intf)
|
||||||
|
go callback()
|
||||||
|
case unix.RTM_DELETE:
|
||||||
|
if intfv4 != nil && route.Gw.Compare(nexthopv4) == 0 || intfv6 != nil && route.Gw.Compare(nexthopv6) == 0 {
|
||||||
|
log.Infof("Network monitor: default route removed: via %s, interface %s", route.Gw, intf)
|
||||||
|
go callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaceMessage(buf []byte) (*route.InterfaceMessage, error) {
|
||||||
|
msgs, err := route.ParseRIB(route.RIBTypeInterface, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse RIB: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msgs) != 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected RIB message msgs: %v", msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, ok := msgs[0].(*route.InterfaceMessage)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected RIB message type: %T", msgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRouteMessage(buf []byte) (*routemanager.Route, error) {
|
||||||
|
msgs, err := route.ParseRIB(route.RIBTypeRoute, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse RIB: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msgs) != 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected RIB message msgs: %v", msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, ok := msgs[0].(*route.RouteMessage)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected RIB message type: %T", msgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return routemanager.MsgToRoute(msg)
|
||||||
|
}
|
||||||
84
client/internal/networkmonitor/monitor_generic.go
Normal file
84
client/internal/networkmonitor/monitor_generic.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
//go:build !ios && !android
|
||||||
|
|
||||||
|
package networkmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start begins monitoring network changes. When a change is detected, it calls the callback asynchronously and returns.
|
||||||
|
func (nw *NetworkMonitor) Start(ctx context.Context, callback func()) (err error) {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
nw.mu.Lock()
|
||||||
|
ctx, nw.cancel = context.WithCancel(ctx)
|
||||||
|
nw.mu.Unlock()
|
||||||
|
|
||||||
|
nw.wg.Add(1)
|
||||||
|
defer nw.wg.Done()
|
||||||
|
|
||||||
|
var nexthop4, nexthop6 netip.Addr
|
||||||
|
var intf4, intf6 *net.Interface
|
||||||
|
|
||||||
|
operation := func() error {
|
||||||
|
var errv4, errv6 error
|
||||||
|
nexthop4, intf4, errv4 = routemanager.GetNextHop(netip.IPv4Unspecified())
|
||||||
|
nexthop6, intf6, errv6 = routemanager.GetNextHop(netip.IPv6Unspecified())
|
||||||
|
|
||||||
|
if errv4 != nil && errv6 != nil {
|
||||||
|
return errors.New("failed to get default next hops")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errv4 == nil {
|
||||||
|
log.Debugf("Network monitor: IPv4 default route: %s, interface: %s", nexthop4, intf4.Name)
|
||||||
|
}
|
||||||
|
if errv6 == nil {
|
||||||
|
log.Debugf("Network monitor: IPv6 default route: %s, interface: %s", nexthop6, intf6.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue if either route was found
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expBackOff := backoff.WithContext(backoff.NewExponentialBackOff(), ctx)
|
||||||
|
|
||||||
|
if err := backoff.Retry(operation, expBackOff); err != nil {
|
||||||
|
return fmt.Errorf("failed to get default next hops: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover in case sys ops panic
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("panic occurred: %v, stack trace: %s", r, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := checkChange(ctx, nexthop4, intf4, nexthop6, intf6, callback); err != nil {
|
||||||
|
return fmt.Errorf("check change: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the network monitor.
|
||||||
|
func (nw *NetworkMonitor) Stop() {
|
||||||
|
nw.mu.Lock()
|
||||||
|
defer nw.mu.Unlock()
|
||||||
|
|
||||||
|
if nw.cancel != nil {
|
||||||
|
nw.cancel()
|
||||||
|
nw.wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
81
client/internal/networkmonitor/monitor_linux.go
Normal file
81
client/internal/networkmonitor/monitor_linux.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
|
package networkmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkChange(ctx context.Context, nexthopv4 netip.Addr, intfv4 *net.Interface, nexthop6 netip.Addr, intfv6 *net.Interface, callback func()) error {
|
||||||
|
if intfv4 == nil && intfv6 == nil {
|
||||||
|
return errors.New("no interfaces available")
|
||||||
|
}
|
||||||
|
|
||||||
|
linkChan := make(chan netlink.LinkUpdate)
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
if err := netlink.LinkSubscribe(linkChan, done); err != nil {
|
||||||
|
return fmt.Errorf("subscribe to link updates: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
routeChan := make(chan netlink.RouteUpdate)
|
||||||
|
if err := netlink.RouteSubscribe(routeChan, done); err != nil {
|
||||||
|
return fmt.Errorf("subscribe to route updates: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Network monitor: started")
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrStopped
|
||||||
|
|
||||||
|
// handle interface state changes
|
||||||
|
case update := <-linkChan:
|
||||||
|
if (intfv4 == nil || update.Index != int32(intfv4.Index)) && (intfv6 == nil || update.Index != int32(intfv6.Index)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch update.Header.Type {
|
||||||
|
case syscall.RTM_DELLINK:
|
||||||
|
log.Infof("Network monitor: monitored interface (%s) is gone", update.Link.Attrs().Name)
|
||||||
|
go callback()
|
||||||
|
return nil
|
||||||
|
case syscall.RTM_NEWLINK:
|
||||||
|
if (update.IfInfomsg.Flags&syscall.IFF_RUNNING) == 0 && update.Link.Attrs().OperState == netlink.OperDown {
|
||||||
|
log.Infof("Network monitor: monitored interface (%s) is down.", update.Link.Attrs().Name)
|
||||||
|
go callback()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle route changes
|
||||||
|
case route := <-routeChan:
|
||||||
|
// default route and main table
|
||||||
|
if route.Dst != nil || route.Table != syscall.RT_TABLE_MAIN {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch route.Type {
|
||||||
|
// triggered on added/replaced routes
|
||||||
|
case syscall.RTM_NEWROUTE:
|
||||||
|
log.Infof("Network monitor: default route changed: via %s, interface %d", route.Gw, route.LinkIndex)
|
||||||
|
go callback()
|
||||||
|
return nil
|
||||||
|
case syscall.RTM_DELROUTE:
|
||||||
|
if intfv4 != nil && route.Gw.Equal(nexthopv4.AsSlice()) || intfv6 != nil && route.Gw.Equal(nexthop6.AsSlice()) {
|
||||||
|
log.Infof("Network monitor: default route removed: via %s, interface %d", route.Gw, route.LinkIndex)
|
||||||
|
go callback()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
client/internal/networkmonitor/monitor_mobile.go
Normal file
12
client/internal/networkmonitor/monitor_mobile.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build ios || android
|
||||||
|
|
||||||
|
package networkmonitor
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func (nw *NetworkMonitor) Start(context.Context, func()) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *NetworkMonitor) Stop() {
|
||||||
|
}
|
||||||
215
client/internal/networkmonitor/monitor_windows.go
Normal file
215
client/internal/networkmonitor/monitor_windows.go
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
package networkmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
unreachable = 0
|
||||||
|
incomplete = 1
|
||||||
|
probe = 2
|
||||||
|
delay = 3
|
||||||
|
stale = 4
|
||||||
|
reachable = 5
|
||||||
|
permanent = 6
|
||||||
|
tbd = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
const interval = 10 * time.Second
|
||||||
|
|
||||||
|
func checkChange(ctx context.Context, nexthopv4 netip.Addr, intfv4 *net.Interface, nexthopv6 netip.Addr, intfv6 *net.Interface, callback func()) error {
|
||||||
|
var neighborv4, neighborv6 *routemanager.Neighbor
|
||||||
|
{
|
||||||
|
initialNeighbors, err := getNeighbors()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get neighbors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, ok := initialNeighbors[nexthopv4]; ok {
|
||||||
|
neighborv4 = &n
|
||||||
|
}
|
||||||
|
if n, ok := initialNeighbors[nexthopv6]; ok {
|
||||||
|
neighborv6 = &n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debugf("Network monitor: initial IPv4 neighbor: %v, IPv6 neighbor: %v", neighborv4, neighborv6)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrStopped
|
||||||
|
case <-ticker.C:
|
||||||
|
if changed(nexthopv4, intfv4, neighborv4, nexthopv6, intfv6, neighborv6) {
|
||||||
|
go callback()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func changed(
|
||||||
|
nexthopv4 netip.Addr,
|
||||||
|
intfv4 *net.Interface,
|
||||||
|
neighborv4 *routemanager.Neighbor,
|
||||||
|
nexthopv6 netip.Addr,
|
||||||
|
intfv6 *net.Interface,
|
||||||
|
neighborv6 *routemanager.Neighbor,
|
||||||
|
) bool {
|
||||||
|
neighbors, err := getNeighbors()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("network monitor: error fetching current neighbors: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if neighborChanged(nexthopv4, neighborv4, neighbors) || neighborChanged(nexthopv6, neighborv6, neighbors) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := getRoutes()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("network monitor: error fetching current routes: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if routeChanged(nexthopv4, intfv4, routes) || routeChanged(nexthopv6, intfv6, routes) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// routeChanged checks if the default routes still point to our nexthop/interface
|
||||||
|
func routeChanged(nexthop netip.Addr, intf *net.Interface, routes map[netip.Prefix]routemanager.Route) bool {
|
||||||
|
if !nexthop.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var unspec netip.Prefix
|
||||||
|
if nexthop.Is6() {
|
||||||
|
unspec = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||||
|
} else {
|
||||||
|
unspec = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r, ok := routes[unspec]; ok {
|
||||||
|
if r.Nexthop != nexthop || compareIntf(r.Interface, intf) != 0 {
|
||||||
|
intf := "<nil>"
|
||||||
|
if r.Interface != nil {
|
||||||
|
intf = r.Interface.Name
|
||||||
|
}
|
||||||
|
log.Infof("network monitor: default route changed: %s via %s (%s)", r.Destination, r.Nexthop, intf)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("network monitor: default route is gone")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func neighborChanged(nexthop netip.Addr, neighbor *routemanager.Neighbor, neighbors map[netip.Addr]routemanager.Neighbor) bool {
|
||||||
|
if neighbor == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider non-local nexthops, e.g. on point-to-point interfaces
|
||||||
|
if n, ok := neighbors[nexthop]; ok {
|
||||||
|
if n.State != reachable && n.State != permanent {
|
||||||
|
log.Infof("network monitor: neighbor %s (%s) is not reachable: %s", neighbor.IPAddress, neighbor.LinkLayerAddress, stateFromInt(n.State))
|
||||||
|
return true
|
||||||
|
} else if n.InterfaceIndex != neighbor.InterfaceIndex {
|
||||||
|
log.Infof(
|
||||||
|
"network monitor: neighbor %s (%s) changed interface from '%s' (%d) to '%s' (%d): %s",
|
||||||
|
neighbor.IPAddress,
|
||||||
|
neighbor.LinkLayerAddress,
|
||||||
|
neighbor.InterfaceAlias,
|
||||||
|
neighbor.InterfaceIndex,
|
||||||
|
n.InterfaceAlias,
|
||||||
|
n.InterfaceIndex,
|
||||||
|
stateFromInt(n.State),
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("network monitor: neighbor %s (%s) is gone", neighbor.IPAddress, neighbor.LinkLayerAddress)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNeighbors() (map[netip.Addr]routemanager.Neighbor, error) {
|
||||||
|
entries, err := routemanager.GetNeighbors()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get neighbors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
neighbours := make(map[netip.Addr]routemanager.Neighbor, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
neighbours[entry.IPAddress] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbours, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRoutes() (map[netip.Prefix]routemanager.Route, error) {
|
||||||
|
entries, err := routemanager.GetRoutes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get routes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := make(map[netip.Prefix]routemanager.Route, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
routes[entry.Destination] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateFromInt(state uint8) string {
|
||||||
|
switch state {
|
||||||
|
case unreachable:
|
||||||
|
return "unreachable"
|
||||||
|
case incomplete:
|
||||||
|
return "incomplete"
|
||||||
|
case probe:
|
||||||
|
return "probe"
|
||||||
|
case delay:
|
||||||
|
return "delay"
|
||||||
|
case stale:
|
||||||
|
return "stale"
|
||||||
|
case reachable:
|
||||||
|
return "reachable"
|
||||||
|
case permanent:
|
||||||
|
return "permanent"
|
||||||
|
case tbd:
|
||||||
|
return "tbd"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareIntf(a, b *net.Interface) int {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if a == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return a.Index - b.Index
|
||||||
|
}
|
||||||
@@ -276,7 +276,7 @@ func (conn *Conn) candidateTypes() []ice.CandidateType {
|
|||||||
// Open opens connection to the remote peer starting ICE candidate gathering process.
|
// Open opens connection to the remote peer starting ICE candidate gathering process.
|
||||||
// Blocks until connection has been closed or connection timeout.
|
// Blocks until connection has been closed or connection timeout.
|
||||||
// ConnStatus will be set accordingly
|
// ConnStatus will be set accordingly
|
||||||
func (conn *Conn) Open() error {
|
func (conn *Conn) Open(ctx context.Context) error {
|
||||||
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
||||||
|
|
||||||
peerState := State{
|
peerState := State{
|
||||||
@@ -336,7 +336,7 @@ func (conn *Conn) Open() error {
|
|||||||
// at this point we received offer/answer and we are ready to gather candidates
|
// at this point we received offer/answer and we are ready to gather candidates
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
conn.status = StatusConnecting
|
conn.status = StatusConnecting
|
||||||
conn.ctx, conn.notifyDisconnected = context.WithCancel(context.Background())
|
conn.ctx, conn.notifyDisconnected = context.WithCancel(ctx)
|
||||||
defer conn.notifyDisconnected()
|
defer conn.notifyDisconnected()
|
||||||
conn.mu.Unlock()
|
conn.mu.Unlock()
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
|
|||||||
var endpoint net.Addr
|
var endpoint net.Addr
|
||||||
if isRelayCandidate(pair.Local) {
|
if isRelayCandidate(pair.Local) {
|
||||||
log.Debugf("setup relay connection")
|
log.Debugf("setup relay connection")
|
||||||
conn.wgProxy = conn.wgProxyFactory.GetProxy()
|
conn.wgProxy = conn.wgProxyFactory.GetProxy(conn.ctx)
|
||||||
endpoint, err = conn.wgProxy.AddTurnConn(remoteConn)
|
endpoint, err = conn.wgProxy.AddTurnConn(remoteConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -448,9 +448,11 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
|
|||||||
err = conn.config.WgConfig.WgInterface.UpdatePeer(conn.config.WgConfig.RemoteKey, conn.config.WgConfig.AllowedIps, defaultWgKeepAlive, endpointUdpAddr, conn.config.WgConfig.PreSharedKey)
|
err = conn.config.WgConfig.WgInterface.UpdatePeer(conn.config.WgConfig.RemoteKey, conn.config.WgConfig.AllowedIps, defaultWgKeepAlive, endpointUdpAddr, conn.config.WgConfig.PreSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if conn.wgProxy != nil {
|
if conn.wgProxy != nil {
|
||||||
_ = conn.wgProxy.CloseConn()
|
if err := conn.wgProxy.CloseConn(); err != nil {
|
||||||
|
log.Warnf("Failed to close turn connection: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, fmt.Errorf("update peer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.status = StatusConnected
|
conn.status = StatusConnected
|
||||||
@@ -485,6 +487,10 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "ios" {
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
if conn.onConnected != nil {
|
if conn.onConnected != nil {
|
||||||
conn.onConnected(conn.config.Key, remoteRosenpassPubKey, ipNet.IP.String(), remoteRosenpassAddr)
|
conn.onConnected(conn.config.Key, remoteRosenpassPubKey, ipNet.IP.String(), remoteRosenpassAddr)
|
||||||
}
|
}
|
||||||
@@ -730,7 +736,7 @@ func (conn *Conn) Close() error {
|
|||||||
// before conn.Open() another update from management arrives with peers: [1,2,3,4,5]
|
// before conn.Open() another update from management arrives with peers: [1,2,3,4,5]
|
||||||
// engine adds a new Conn for 4 and 5
|
// engine adds a new Conn for 4 and 5
|
||||||
// therefore peer 4 has 2 Conn objects
|
// therefore peer 4 has 2 Conn objects
|
||||||
log.Warnf("connection has been already closed or attempted closing not started coonection %s", conn.config.Key)
|
log.Warnf("Connection has been already closed or attempted closing not started connection %s", conn.config.Key)
|
||||||
return NewConnectionAlreadyClosed(conn.config.Key)
|
return NewConnectionAlreadyClosed(conn.config.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package peer
|
package peer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -35,7 +36,7 @@ func TestNewConn_interfaceFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_GetKey(t *testing.T) {
|
func TestConn_GetKey(t *testing.T) {
|
||||||
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = wgProxyFactory.Free()
|
_ = wgProxyFactory.Free()
|
||||||
}()
|
}()
|
||||||
@@ -50,7 +51,7 @@ func TestConn_GetKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_OnRemoteOffer(t *testing.T) {
|
func TestConn_OnRemoteOffer(t *testing.T) {
|
||||||
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = wgProxyFactory.Free()
|
_ = wgProxyFactory.Free()
|
||||||
}()
|
}()
|
||||||
@@ -87,7 +88,7 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_OnRemoteAnswer(t *testing.T) {
|
func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||||
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = wgProxyFactory.Free()
|
_ = wgProxyFactory.Free()
|
||||||
}()
|
}()
|
||||||
@@ -123,7 +124,7 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
func TestConn_Status(t *testing.T) {
|
func TestConn_Status(t *testing.T) {
|
||||||
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = wgProxyFactory.Free()
|
_ = wgProxyFactory.Free()
|
||||||
}()
|
}()
|
||||||
@@ -153,7 +154,7 @@ func TestConn_Status(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_Close(t *testing.T) {
|
func TestConn_Close(t *testing.T) {
|
||||||
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = wgProxyFactory.Free()
|
_ = wgProxyFactory.Free()
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package routemanager
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ type clientNetwork struct {
|
|||||||
stop context.CancelFunc
|
stop context.CancelFunc
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
routes map[string]*route.Route
|
routes map[route.ID]*route.Route
|
||||||
routeUpdate chan routesUpdate
|
routeUpdate chan routesUpdate
|
||||||
peerStateUpdate chan struct{}
|
peerStateUpdate chan struct{}
|
||||||
routePeersNotifiers map[string]chan struct{}
|
routePeersNotifiers map[string]chan struct{}
|
||||||
@@ -49,7 +50,7 @@ func newClientNetworkWatcher(ctx context.Context, wgInterface *iface.WGIface, st
|
|||||||
stop: cancel,
|
stop: cancel,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
routes: make(map[string]*route.Route),
|
routes: make(map[route.ID]*route.Route),
|
||||||
routePeersNotifiers: make(map[string]chan struct{}),
|
routePeersNotifiers: make(map[string]chan struct{}),
|
||||||
routeUpdate: make(chan routesUpdate),
|
routeUpdate: make(chan routesUpdate),
|
||||||
peerStateUpdate: make(chan struct{}),
|
peerStateUpdate: make(chan struct{}),
|
||||||
@@ -58,8 +59,8 @@ func newClientNetworkWatcher(ctx context.Context, wgInterface *iface.WGIface, st
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientNetwork) getRouterPeerStatuses() map[string]routerPeerStatus {
|
func (c *clientNetwork) getRouterPeerStatuses() map[route.ID]routerPeerStatus {
|
||||||
routePeerStatuses := make(map[string]routerPeerStatus)
|
routePeerStatuses := make(map[route.ID]routerPeerStatus)
|
||||||
for _, r := range c.routes {
|
for _, r := range c.routes {
|
||||||
peerStatus, err := c.statusRecorder.GetPeer(r.Peer)
|
peerStatus, err := c.statusRecorder.GetPeer(r.Peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -89,12 +90,12 @@ func (c *clientNetwork) getRouterPeerStatuses() map[string]routerPeerStatus {
|
|||||||
// * Latency: Routes with lower latency are prioritized.
|
// * Latency: Routes with lower latency are prioritized.
|
||||||
//
|
//
|
||||||
// It returns the ID of the selected optimal route.
|
// It returns the ID of the selected optimal route.
|
||||||
func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]routerPeerStatus) string {
|
func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[route.ID]routerPeerStatus) route.ID {
|
||||||
chosen := ""
|
chosen := route.ID("")
|
||||||
chosenScore := float64(0)
|
chosenScore := float64(0)
|
||||||
currScore := float64(0)
|
currScore := float64(0)
|
||||||
|
|
||||||
currID := ""
|
currID := route.ID("")
|
||||||
if c.chosenRoute != nil {
|
if c.chosenRoute != nil {
|
||||||
currID = c.chosenRoute.ID
|
currID = c.chosenRoute.ID
|
||||||
}
|
}
|
||||||
@@ -152,11 +153,16 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro
|
|||||||
|
|
||||||
log.Warnf("the network %s has not been assigned a routing peer as no peers from the list %s are currently connected", c.network, peers)
|
log.Warnf("the network %s has not been assigned a routing peer as no peers from the list %s are currently connected", c.network, peers)
|
||||||
case chosen != currID:
|
case chosen != currID:
|
||||||
if currScore != 0 && currScore < chosenScore+0.1 {
|
// we compare the current score + 10ms to the chosen score to avoid flapping between routes
|
||||||
|
if currScore != 0 && currScore+0.01 > chosenScore {
|
||||||
|
log.Debugf("keeping current routing peer because the score difference with latency is less than 0.01(10ms), current: %f, new: %f", currScore, chosenScore)
|
||||||
return currID
|
return currID
|
||||||
} else {
|
|
||||||
log.Infof("new chosen route is %s with peer %s with score %f for network %s", chosen, c.routes[chosen].Peer, chosenScore, c.network)
|
|
||||||
}
|
}
|
||||||
|
var p string
|
||||||
|
if rt := c.routes[chosen]; rt != nil {
|
||||||
|
p = rt.Peer
|
||||||
|
}
|
||||||
|
log.Infof("new chosen route is %s with peer %s with score %f for network %s", chosen, p, chosenScore, c.network)
|
||||||
}
|
}
|
||||||
|
|
||||||
return chosen
|
return chosen
|
||||||
@@ -215,7 +221,7 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
|
|||||||
|
|
||||||
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
||||||
if c.chosenRoute != nil {
|
if c.chosenRoute != nil {
|
||||||
if err := removeVPNRoute(c.network, c.wgInterface.Name()); err != nil {
|
if err := removeVPNRoute(c.network, c.getAsInterface()); err != nil {
|
||||||
return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
|
return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +262,7 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise add the route to the system
|
// otherwise add the route to the system
|
||||||
if err := addVPNRoute(c.network, c.wgInterface.Name()); err != nil {
|
if err := addVPNRoute(c.network, c.getAsInterface()); err != nil {
|
||||||
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
||||||
c.network.String(), c.wgInterface.Address().IP.String(), err)
|
c.network.String(), c.wgInterface.Address().IP.String(), err)
|
||||||
}
|
}
|
||||||
@@ -289,7 +295,7 @@ func (c *clientNetwork) sendUpdateToClientNetworkWatcher(update routesUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientNetwork) handleUpdate(update routesUpdate) {
|
func (c *clientNetwork) handleUpdate(update routesUpdate) {
|
||||||
updateMap := make(map[string]*route.Route)
|
updateMap := make(map[route.ID]*route.Route)
|
||||||
|
|
||||||
for _, r := range update.routes {
|
for _, r := range update.routes {
|
||||||
updateMap[r.ID] = r
|
updateMap[r.ID] = r
|
||||||
@@ -344,3 +350,15 @@ func (c *clientNetwork) peersStateAndUpdateWatcher() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) getAsInterface() *net.Interface {
|
||||||
|
intf, err := net.InterfaceByName(c.wgInterface.Name())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Couldn't get interface by name %s: %v", c.wgInterface.Name(), err)
|
||||||
|
intf = &net.Interface{
|
||||||
|
Name: c.wgInterface.Name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intf
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,21 +12,21 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
statuses map[string]routerPeerStatus
|
statuses map[route.ID]routerPeerStatus
|
||||||
expectedRouteID string
|
expectedRouteID route.ID
|
||||||
currentRoute string
|
currentRoute route.ID
|
||||||
existingRoutes map[string]*route.Route
|
existingRoutes map[route.ID]*route.Route
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "one route",
|
name: "one route",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
direct: true,
|
direct: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -38,14 +38,14 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one connected routes with relayed and direct",
|
name: "one connected routes with relayed and direct",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: true,
|
relayed: true,
|
||||||
direct: true,
|
direct: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -57,14 +57,14 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one connected routes with relayed and no direct",
|
name: "one connected routes with relayed and no direct",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: true,
|
relayed: true,
|
||||||
direct: false,
|
direct: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -76,14 +76,14 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no connected peers",
|
name: "no connected peers",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: false,
|
connected: false,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
direct: false,
|
direct: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -95,7 +95,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple connected peers with different metrics",
|
name: "multiple connected peers with different metrics",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
@@ -107,7 +107,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
direct: true,
|
direct: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: 9000,
|
Metric: 9000,
|
||||||
@@ -124,7 +124,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple connected peers with one relayed",
|
name: "multiple connected peers with one relayed",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
@@ -136,7 +136,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
direct: true,
|
direct: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -153,7 +153,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple connected peers with one direct",
|
name: "multiple connected peers with one direct",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
@@ -165,7 +165,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
direct: false,
|
direct: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -182,7 +182,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple connected peers with different latencies",
|
name: "multiple connected peers with different latencies",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
latency: 300 * time.Millisecond,
|
latency: 300 * time.Millisecond,
|
||||||
@@ -192,7 +192,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
latency: 10 * time.Millisecond,
|
latency: 10 * time.Millisecond,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -209,7 +209,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "should ignore routes with latency 0",
|
name: "should ignore routes with latency 0",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
latency: 0 * time.Millisecond,
|
latency: 0 * time.Millisecond,
|
||||||
@@ -219,7 +219,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
latency: 10 * time.Millisecond,
|
latency: 10 * time.Millisecond,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -236,12 +236,12 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "current route with similar score and similar but slightly worse latency should not change",
|
name: "current route with similar score and similar but slightly worse latency should not change",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
direct: true,
|
direct: true,
|
||||||
latency: 12 * time.Millisecond,
|
latency: 15 * time.Millisecond,
|
||||||
},
|
},
|
||||||
"route2": {
|
"route2": {
|
||||||
connected: true,
|
connected: true,
|
||||||
@@ -250,7 +250,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
latency: 10 * time.Millisecond,
|
latency: 10 * time.Millisecond,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
@@ -265,9 +265,40 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
currentRoute: "route1",
|
currentRoute: "route1",
|
||||||
expectedRouteID: "route1",
|
expectedRouteID: "route1",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "current route with bad score should be changed to route with better score",
|
||||||
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
|
"route1": {
|
||||||
|
connected: true,
|
||||||
|
relayed: false,
|
||||||
|
direct: true,
|
||||||
|
latency: 200 * time.Millisecond,
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
connected: true,
|
||||||
|
relayed: false,
|
||||||
|
direct: true,
|
||||||
|
latency: 10 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
|
"route1": {
|
||||||
|
ID: "route1",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer1",
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
ID: "route2",
|
||||||
|
Metric: route.MaxMetric,
|
||||||
|
Peer: "peer2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentRoute: "route1",
|
||||||
|
expectedRouteID: "route2",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "current chosen route doesn't exist anymore",
|
name: "current chosen route doesn't exist anymore",
|
||||||
statuses: map[string]routerPeerStatus{
|
statuses: map[route.ID]routerPeerStatus{
|
||||||
"route1": {
|
"route1": {
|
||||||
connected: true,
|
connected: true,
|
||||||
relayed: false,
|
relayed: false,
|
||||||
@@ -281,7 +312,7 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
|
|||||||
latency: 10 * time.Millisecond,
|
latency: 10 * time.Millisecond,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existingRoutes: map[string]*route.Route{
|
existingRoutes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Metric: route.MaxMetric,
|
Metric: route.MaxMetric,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routeselector"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
@@ -28,7 +29,9 @@ var defaultv6 = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
|||||||
// Manager is a route manager interface
|
// Manager is a route manager interface
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error)
|
Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error)
|
||||||
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error
|
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap, error)
|
||||||
|
TriggerSelection(route.HAMap)
|
||||||
|
GetRouteSelector() *routeselector.RouteSelector
|
||||||
SetRouteChangeListener(listener listener.NetworkChangeListener)
|
SetRouteChangeListener(listener listener.NetworkChangeListener)
|
||||||
InitialRouteRange() []string
|
InitialRouteRange() []string
|
||||||
EnableServerRouter(firewall firewall.Manager) error
|
EnableServerRouter(firewall firewall.Manager) error
|
||||||
@@ -40,7 +43,8 @@ type DefaultManager struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
stop context.CancelFunc
|
stop context.CancelFunc
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
clientNetworks map[string]*clientNetwork
|
clientNetworks map[route.HAUniqueID]*clientNetwork
|
||||||
|
routeSelector *routeselector.RouteSelector
|
||||||
serverRouter serverRouter
|
serverRouter serverRouter
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
@@ -53,7 +57,8 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
|
|||||||
dm := &DefaultManager{
|
dm := &DefaultManager{
|
||||||
ctx: mCTX,
|
ctx: mCTX,
|
||||||
stop: cancel,
|
stop: cancel,
|
||||||
clientNetworks: make(map[string]*clientNetwork),
|
clientNetworks: make(map[route.HAUniqueID]*clientNetwork),
|
||||||
|
routeSelector: routeselector.NewRouteSelector(),
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
pubKey: pubKey,
|
pubKey: pubKey,
|
||||||
@@ -117,28 +122,29 @@ func (m *DefaultManager) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRoutes compares received routes with existing routes and removes, updates or adds them to the client and server maps
|
// UpdateRoutes compares received routes with existing routes and removes, updates or adds them to the client and server maps
|
||||||
func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error {
|
func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap, error) {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
log.Infof("not updating routes as context is closed")
|
log.Infof("not updating routes as context is closed")
|
||||||
return m.ctx.Err()
|
return nil, nil, m.ctx.Err()
|
||||||
default:
|
default:
|
||||||
m.mux.Lock()
|
m.mux.Lock()
|
||||||
defer m.mux.Unlock()
|
defer m.mux.Unlock()
|
||||||
|
|
||||||
newServerRoutesMap, newClientRoutesIDMap := m.classifiesRoutes(newRoutes)
|
newServerRoutesMap, newClientRoutesIDMap := m.classifyRoutes(newRoutes)
|
||||||
|
|
||||||
m.updateClientNetworks(updateSerial, newClientRoutesIDMap)
|
filteredClientRoutes := m.routeSelector.FilterSelected(newClientRoutesIDMap)
|
||||||
m.notifier.onNewRoutes(newClientRoutesIDMap)
|
m.updateClientNetworks(updateSerial, filteredClientRoutes)
|
||||||
|
m.notifier.onNewRoutes(filteredClientRoutes)
|
||||||
|
|
||||||
if m.serverRouter != nil {
|
if m.serverRouter != nil {
|
||||||
err := m.serverRouter.updateRoutes(newServerRoutesMap)
|
err := m.serverRouter.updateRoutes(newServerRoutesMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("update routes: %w", err)
|
return nil, nil, fmt.Errorf("update routes: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return newServerRoutesMap, newClientRoutesIDMap, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,19 +155,57 @@ func (m *DefaultManager) SetRouteChangeListener(listener listener.NetworkChangeL
|
|||||||
|
|
||||||
// InitialRouteRange return the list of initial routes. It used by mobile systems
|
// InitialRouteRange return the list of initial routes. It used by mobile systems
|
||||||
func (m *DefaultManager) InitialRouteRange() []string {
|
func (m *DefaultManager) InitialRouteRange() []string {
|
||||||
return m.notifier.initialRouteRanges()
|
return m.notifier.getInitialRouteRanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DefaultManager) updateClientNetworks(updateSerial uint64, networks map[string][]*route.Route) {
|
// GetRouteSelector returns the route selector
|
||||||
// removing routes that do not exist as per the update from the Management service.
|
func (m *DefaultManager) GetRouteSelector() *routeselector.RouteSelector {
|
||||||
|
return m.routeSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientRoutes returns the client routes
|
||||||
|
func (m *DefaultManager) GetClientRoutes() map[route.HAUniqueID]*clientNetwork {
|
||||||
|
return m.clientNetworks
|
||||||
|
}
|
||||||
|
|
||||||
|
// TriggerSelection triggers the selection of routes, stopping deselected watchers and starting newly selected ones
|
||||||
|
func (m *DefaultManager) TriggerSelection(networks route.HAMap) {
|
||||||
|
m.mux.Lock()
|
||||||
|
defer m.mux.Unlock()
|
||||||
|
|
||||||
|
networks = m.routeSelector.FilterSelected(networks)
|
||||||
|
|
||||||
|
m.notifier.onNewRoutes(networks)
|
||||||
|
|
||||||
|
m.stopObsoleteClients(networks)
|
||||||
|
|
||||||
|
for id, routes := range networks {
|
||||||
|
if _, found := m.clientNetworks[id]; found {
|
||||||
|
// don't touch existing client network watchers
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
clientNetworkWatcher := newClientNetworkWatcher(m.ctx, m.wgInterface, m.statusRecorder, routes[0].Network)
|
||||||
|
m.clientNetworks[id] = clientNetworkWatcher
|
||||||
|
go clientNetworkWatcher.peersStateAndUpdateWatcher()
|
||||||
|
clientNetworkWatcher.sendUpdateToClientNetworkWatcher(routesUpdate{routes: routes})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopObsoleteClients stops the client network watcher for the networks that are not in the new list
|
||||||
|
func (m *DefaultManager) stopObsoleteClients(networks route.HAMap) {
|
||||||
for id, client := range m.clientNetworks {
|
for id, client := range m.clientNetworks {
|
||||||
_, found := networks[id]
|
if _, ok := networks[id]; !ok {
|
||||||
if !found {
|
log.Debugf("Stopping client network watcher, %s", id)
|
||||||
log.Debugf("stopping client network watcher, %s", id)
|
|
||||||
client.stop()
|
client.stop()
|
||||||
delete(m.clientNetworks, id)
|
delete(m.clientNetworks, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DefaultManager) updateClientNetworks(updateSerial uint64, networks route.HAMap) {
|
||||||
|
// removing routes that do not exist as per the update from the Management service.
|
||||||
|
m.stopObsoleteClients(networks)
|
||||||
|
|
||||||
for id, routes := range networks {
|
for id, routes := range networks {
|
||||||
clientNetworkWatcher, found := m.clientNetworks[id]
|
clientNetworkWatcher, found := m.clientNetworks[id]
|
||||||
@@ -178,15 +222,15 @@ func (m *DefaultManager) updateClientNetworks(updateSerial uint64, networks map[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DefaultManager) classifiesRoutes(newRoutes []*route.Route) (map[string]*route.Route, map[string][]*route.Route) {
|
func (m *DefaultManager) classifyRoutes(newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap) {
|
||||||
newClientRoutesIDMap := make(map[string][]*route.Route)
|
newClientRoutesIDMap := make(route.HAMap)
|
||||||
newServerRoutesMap := make(map[string]*route.Route)
|
newServerRoutesMap := make(map[route.ID]*route.Route)
|
||||||
ownNetworkIDs := make(map[string]bool)
|
ownNetworkIDs := make(map[route.HAUniqueID]bool)
|
||||||
|
|
||||||
for _, newRoute := range newRoutes {
|
for _, newRoute := range newRoutes {
|
||||||
networkID := route.GetHAUniqueID(newRoute)
|
haID := route.GetHAUniqueID(newRoute)
|
||||||
if newRoute.Peer == m.pubKey {
|
if newRoute.Peer == m.pubKey {
|
||||||
ownNetworkIDs[networkID] = true
|
ownNetworkIDs[haID] = true
|
||||||
// only linux is supported for now
|
// only linux is supported for now
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
log.Warnf("received a route to manage, but agent doesn't support router mode on %s OS", runtime.GOOS)
|
log.Warnf("received a route to manage, but agent doesn't support router mode on %s OS", runtime.GOOS)
|
||||||
@@ -197,12 +241,12 @@ func (m *DefaultManager) classifiesRoutes(newRoutes []*route.Route) (map[string]
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, newRoute := range newRoutes {
|
for _, newRoute := range newRoutes {
|
||||||
networkID := route.GetHAUniqueID(newRoute)
|
haID := route.GetHAUniqueID(newRoute)
|
||||||
if !ownNetworkIDs[networkID] {
|
if !ownNetworkIDs[haID] {
|
||||||
if !isPrefixSupported(newRoute.Network) {
|
if !isPrefixSupported(newRoute.Network) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newClientRoutesIDMap[networkID] = append(newClientRoutesIDMap[networkID], newRoute)
|
newClientRoutesIDMap[haID] = append(newClientRoutesIDMap[haID], newRoute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +254,7 @@ func (m *DefaultManager) classifiesRoutes(newRoutes []*route.Route) (map[string]
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *DefaultManager) clientRoutes(initialRoutes []*route.Route) []*route.Route {
|
func (m *DefaultManager) clientRoutes(initialRoutes []*route.Route) []*route.Route {
|
||||||
_, crMap := m.classifiesRoutes(initialRoutes)
|
_, crMap := m.classifyRoutes(initialRoutes)
|
||||||
rs := make([]*route.Route, 0)
|
rs := make([]*route.Route, 0)
|
||||||
for _, routes := range crMap {
|
for _, routes := range crMap {
|
||||||
rs = append(rs, routes...)
|
rs = append(rs, routes...)
|
||||||
@@ -220,10 +264,7 @@ func (m *DefaultManager) clientRoutes(initialRoutes []*route.Route) []*route.Rou
|
|||||||
|
|
||||||
func isPrefixSupported(prefix netip.Prefix) bool {
|
func isPrefixSupported(prefix netip.Prefix) bool {
|
||||||
if !nbnet.CustomRoutingDisabled() {
|
if !nbnet.CustomRoutingDisabled() {
|
||||||
switch runtime.GOOS {
|
return true
|
||||||
case "linux", "windows", "darwin":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If prefix is too small, lets assume it is a possible default prefix which is not yet supported
|
// If prefix is too small, lets assume it is a possible default prefix which is not yet supported
|
||||||
|
|||||||
@@ -428,11 +428,11 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(testCase.inputInitRoutes) > 0 {
|
if len(testCase.inputInitRoutes) > 0 {
|
||||||
err = routeManager.UpdateRoutes(testCase.inputSerial, testCase.inputRoutes)
|
_, _, err = routeManager.UpdateRoutes(testCase.inputSerial, testCase.inputRoutes)
|
||||||
require.NoError(t, err, "should update routes with init routes")
|
require.NoError(t, err, "should update routes with init routes")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = routeManager.UpdateRoutes(testCase.inputSerial+uint64(len(testCase.inputInitRoutes)), testCase.inputRoutes)
|
_, _, err = routeManager.UpdateRoutes(testCase.inputSerial+uint64(len(testCase.inputInitRoutes)), testCase.inputRoutes)
|
||||||
require.NoError(t, err, "should update routes")
|
require.NoError(t, err, "should update routes")
|
||||||
|
|
||||||
expectedWatchers := testCase.clientNetworkWatchersExpected
|
expectedWatchers := testCase.clientNetworkWatchersExpected
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ import (
|
|||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routeselector"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockManager is the mock instance of a route manager
|
// MockManager is the mock instance of a route manager
|
||||||
type MockManager struct {
|
type MockManager struct {
|
||||||
UpdateRoutesFunc func(updateSerial uint64, newRoutes []*route.Route) error
|
UpdateRoutesFunc func(updateSerial uint64, newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap, error)
|
||||||
StopFunc func()
|
TriggerSelectionFunc func(haMap route.HAMap)
|
||||||
|
GetRouteSelectorFunc func() *routeselector.RouteSelector
|
||||||
|
StopFunc func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
func (m *MockManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
@@ -27,11 +30,25 @@ func (m *MockManager) InitialRouteRange() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRoutes mock implementation of UpdateRoutes from Manager interface
|
// UpdateRoutes mock implementation of UpdateRoutes from Manager interface
|
||||||
func (m *MockManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error {
|
func (m *MockManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap, error) {
|
||||||
if m.UpdateRoutesFunc != nil {
|
if m.UpdateRoutesFunc != nil {
|
||||||
return m.UpdateRoutesFunc(updateSerial, newRoutes)
|
return m.UpdateRoutesFunc(updateSerial, newRoutes)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("method UpdateRoutes is not implemented")
|
return nil, nil, fmt.Errorf("method UpdateRoutes is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockManager) TriggerSelection(networks route.HAMap) {
|
||||||
|
if m.TriggerSelectionFunc != nil {
|
||||||
|
m.TriggerSelectionFunc(networks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRouteSelector mock implementation of GetRouteSelector from Manager interface
|
||||||
|
func (m *MockManager) GetRouteSelector() *routeselector.RouteSelector {
|
||||||
|
if m.GetRouteSelectorFunc != nil {
|
||||||
|
return m.GetRouteSelectorFunc()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start mock implementation of Start from Manager interface
|
// Start mock implementation of Start from Manager interface
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -10,8 +11,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type notifier struct {
|
type notifier struct {
|
||||||
initialRouteRangers []string
|
initialRouteRanges []string
|
||||||
routeRangers []string
|
routeRanges []string
|
||||||
|
|
||||||
listener listener.NetworkChangeListener
|
listener listener.NetworkChangeListener
|
||||||
listenerMux sync.Mutex
|
listenerMux sync.Mutex
|
||||||
@@ -33,10 +34,10 @@ func (n *notifier) setInitialClientRoutes(clientRoutes []*route.Route) {
|
|||||||
nets = append(nets, r.Network.String())
|
nets = append(nets, r.Network.String())
|
||||||
}
|
}
|
||||||
sort.Strings(nets)
|
sort.Strings(nets)
|
||||||
n.initialRouteRangers = nets
|
n.initialRouteRanges = nets
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *notifier) onNewRoutes(idMap map[string][]*route.Route) {
|
func (n *notifier) onNewRoutes(idMap route.HAMap) {
|
||||||
newNets := make([]string, 0)
|
newNets := make([]string, 0)
|
||||||
for _, routes := range idMap {
|
for _, routes := range idMap {
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
@@ -45,11 +46,18 @@ func (n *notifier) onNewRoutes(idMap map[string][]*route.Route) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(newNets)
|
sort.Strings(newNets)
|
||||||
if !n.hasDiff(n.initialRouteRangers, newNets) {
|
switch runtime.GOOS {
|
||||||
return
|
case "android":
|
||||||
|
if !n.hasDiff(n.initialRouteRanges, newNets) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !n.hasDiff(n.routeRanges, newNets) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n.routeRangers = newNets
|
n.routeRanges = newNets
|
||||||
|
|
||||||
n.notify()
|
n.notify()
|
||||||
}
|
}
|
||||||
@@ -62,7 +70,7 @@ func (n *notifier) notify() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func(l listener.NetworkChangeListener) {
|
go func(l listener.NetworkChangeListener) {
|
||||||
l.OnNetworkChanged(strings.Join(n.routeRangers, ","))
|
l.OnNetworkChanged(strings.Join(addIPv6RangeIfNeeded(n.routeRanges), ","))
|
||||||
}(n.listener)
|
}(n.listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +86,20 @@ func (n *notifier) hasDiff(a []string, b []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *notifier) initialRouteRanges() []string {
|
func (n *notifier) getInitialRouteRanges() []string {
|
||||||
return n.initialRouteRangers
|
return addIPv6RangeIfNeeded(n.initialRouteRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addIPv6RangeIfNeeded returns the input ranges with the default IPv6 range when there is an IPv4 default route.
|
||||||
|
func addIPv6RangeIfNeeded(inputRanges []string) []string {
|
||||||
|
ranges := inputRanges
|
||||||
|
for _, r := range inputRanges {
|
||||||
|
// we are intentionally adding the ipv6 default range in case of ipv4 default range
|
||||||
|
// to ensure that all traffic is managed by the tunnel interface on android
|
||||||
|
if r == "0.0.0.0/0" {
|
||||||
|
ranges = append(ranges, "::/0")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ranges
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package routemanager
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ import (
|
|||||||
type ref struct {
|
type ref struct {
|
||||||
count int
|
count int
|
||||||
nexthop netip.Addr
|
nexthop netip.Addr
|
||||||
intf string
|
intf *net.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteManager struct {
|
type RouteManager struct {
|
||||||
@@ -30,8 +31,8 @@ type RouteManager struct {
|
|||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddRouteFunc func(prefix netip.Prefix) (nexthop netip.Addr, intf string, err error)
|
type AddRouteFunc func(prefix netip.Prefix) (nexthop netip.Addr, intf *net.Interface, err error)
|
||||||
type RemoveRouteFunc func(prefix netip.Prefix, nexthop netip.Addr, intf string) error
|
type RemoveRouteFunc func(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error
|
||||||
|
|
||||||
func NewRouteManager(addRoute AddRouteFunc, removeRoute RemoveRouteFunc) *RouteManager {
|
func NewRouteManager(addRoute AddRouteFunc, removeRoute RemoveRouteFunc) *RouteManager {
|
||||||
// TODO: read initial routing table into refCountMap
|
// TODO: read initial routing table into refCountMap
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package routemanager
|
|||||||
import "github.com/netbirdio/netbird/route"
|
import "github.com/netbirdio/netbird/route"
|
||||||
|
|
||||||
type serverRouter interface {
|
type serverRouter interface {
|
||||||
updateRoutes(map[string]*route.Route) error
|
updateRoutes(map[route.ID]*route.Route) error
|
||||||
removeFromServerNetwork(*route.Route) error
|
removeFromServerNetwork(*route.Route) error
|
||||||
cleanUp()
|
cleanUp()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
type defaultServerRouter struct {
|
type defaultServerRouter struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
routes map[string]*route.Route
|
routes map[route.ID]*route.Route
|
||||||
firewall firewall.Manager
|
firewall firewall.Manager
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
@@ -28,15 +28,15 @@ type defaultServerRouter struct {
|
|||||||
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager, statusRecorder *peer.Status) (serverRouter, error) {
|
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager, statusRecorder *peer.Status) (serverRouter, error) {
|
||||||
return &defaultServerRouter{
|
return &defaultServerRouter{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
routes: make(map[string]*route.Route),
|
routes: make(map[route.ID]*route.Route),
|
||||||
firewall: firewall,
|
firewall: firewall,
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) error {
|
func (m *defaultServerRouter) updateRoutes(routesMap map[route.ID]*route.Route) error {
|
||||||
serverRoutesToRemove := make([]string, 0)
|
serverRoutesToRemove := make([]route.ID, 0)
|
||||||
|
|
||||||
for routeID := range m.routes {
|
for routeID := range m.routes {
|
||||||
update, found := routesMap[routeID]
|
update, found := routesMap[routeID]
|
||||||
@@ -168,7 +168,7 @@ func routeToRouterPair(source string, route *route.Route) (firewall.RouterPair,
|
|||||||
return firewall.RouterPair{}, err
|
return firewall.RouterPair{}, err
|
||||||
}
|
}
|
||||||
return firewall.RouterPair{
|
return firewall.RouterPair{
|
||||||
ID: route.ID,
|
ID: string(route.ID),
|
||||||
Source: parsed.String(),
|
Source: parsed.String(),
|
||||||
Destination: route.Network.Masked().String(),
|
Destination: route.Network.Masked().String(),
|
||||||
Masquerade: route.Masquerade,
|
Masquerade: route.Masquerade,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
|||||||
addr = netip.IPv6Unspecified()
|
addr = netip.IPv6Unspecified()
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultGateway, _, err := getNextHop(addr)
|
defaultGateway, _, err := GetNextHop(addr)
|
||||||
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
return fmt.Errorf("get existing route gateway: %s", err)
|
return fmt.Errorf("get existing route gateway: %s", err)
|
||||||
}
|
}
|
||||||
@@ -60,31 +60,27 @@ func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitIntf string
|
gatewayHop, intf, err := GetNextHop(defaultGateway)
|
||||||
gatewayHop, intf, err := getNextHop(defaultGateway)
|
|
||||||
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err)
|
return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err)
|
||||||
}
|
}
|
||||||
if intf != nil {
|
|
||||||
exitIntf = intf.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, gatewayHop)
|
log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, gatewayHop)
|
||||||
return addToRouteTable(gatewayPrefix, gatewayHop, exitIntf)
|
return addToRouteTable(gatewayPrefix, gatewayHop, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNextHop(ip netip.Addr) (netip.Addr, *net.Interface, error) {
|
func GetNextHop(ip netip.Addr) (netip.Addr, *net.Interface, error) {
|
||||||
r, err := netroute.New()
|
r, err := netroute.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Addr{}, nil, fmt.Errorf("new netroute: %w", err)
|
return netip.Addr{}, nil, fmt.Errorf("new netroute: %w", err)
|
||||||
}
|
}
|
||||||
intf, gateway, preferredSrc, err := r.Route(ip.AsSlice())
|
intf, gateway, preferredSrc, err := r.Route(ip.AsSlice())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to get route for %s: %v", ip, err)
|
log.Debugf("Failed to get route for %s: %v", ip, err)
|
||||||
return netip.Addr{}, nil, ErrRouteNotFound
|
return netip.Addr{}, nil, ErrRouteNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Route for %s: interface %v, nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc)
|
log.Debugf("Route for %s: interface %v nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc)
|
||||||
if gateway == nil {
|
if gateway == nil {
|
||||||
if preferredSrc == nil {
|
if preferredSrc == nil {
|
||||||
return netip.Addr{}, nil, ErrRouteNotFound
|
return netip.Addr{}, nil, ErrRouteNotFound
|
||||||
@@ -153,12 +149,7 @@ func isSubRange(prefix netip.Prefix) (bool, error) {
|
|||||||
|
|
||||||
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
||||||
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
||||||
func addRouteToNonVPNIntf(
|
func addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIface, initialNextHop netip.Addr, initialIntf *net.Interface) (netip.Addr, *net.Interface, error) {
|
||||||
prefix netip.Prefix,
|
|
||||||
vpnIntf *iface.WGIface,
|
|
||||||
initialNextHop netip.Addr,
|
|
||||||
initialIntf *net.Interface,
|
|
||||||
) (netip.Addr, string, error) {
|
|
||||||
addr := prefix.Addr()
|
addr := prefix.Addr()
|
||||||
switch {
|
switch {
|
||||||
case addr.IsLoopback(),
|
case addr.IsLoopback(),
|
||||||
@@ -168,39 +159,34 @@ func addRouteToNonVPNIntf(
|
|||||||
addr.IsUnspecified(),
|
addr.IsUnspecified(),
|
||||||
addr.IsMulticast():
|
addr.IsMulticast():
|
||||||
|
|
||||||
return netip.Addr{}, "", ErrRouteNotAllowed
|
return netip.Addr{}, nil, ErrRouteNotAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the exit interface and next hop for the prefix, so we can add a specific route
|
// Determine the exit interface and next hop for the prefix, so we can add a specific route
|
||||||
nexthop, intf, err := getNextHop(addr)
|
nexthop, intf, err := GetNextHop(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Addr{}, "", fmt.Errorf("get next hop: %w", err)
|
return netip.Addr{}, nil, fmt.Errorf("get next hop: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop, prefix, intf)
|
log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop, prefix, intf)
|
||||||
exitNextHop := nexthop
|
exitNextHop := nexthop
|
||||||
var exitIntf string
|
exitIntf := intf
|
||||||
if intf != nil {
|
|
||||||
exitIntf = intf.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
vpnAddr, ok := netip.AddrFromSlice(vpnIntf.Address().IP)
|
vpnAddr, ok := netip.AddrFromSlice(vpnIntf.Address().IP)
|
||||||
if !ok {
|
if !ok {
|
||||||
return netip.Addr{}, "", fmt.Errorf("failed to convert vpn address to netip.Addr")
|
return netip.Addr{}, nil, fmt.Errorf("failed to convert vpn address to netip.Addr")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if next hop is the VPN address or the interface is the VPN interface, we should use the initial values
|
// if next hop is the VPN address or the interface is the VPN interface, we should use the initial values
|
||||||
if exitNextHop == vpnAddr || exitIntf == vpnIntf.Name() {
|
if exitNextHop == vpnAddr || exitIntf != nil && exitIntf.Name == vpnIntf.Name() {
|
||||||
log.Debugf("Route for prefix %s is pointing to the VPN interface", prefix)
|
log.Debugf("Route for prefix %s is pointing to the VPN interface", prefix)
|
||||||
exitNextHop = initialNextHop
|
exitNextHop = initialNextHop
|
||||||
if initialIntf != nil {
|
exitIntf = initialIntf
|
||||||
exitIntf = initialIntf.Name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Adding a new route for prefix %s with next hop %s", prefix, exitNextHop)
|
log.Debugf("Adding a new route for prefix %s with next hop %s", prefix, exitNextHop)
|
||||||
if err := addToRouteTable(prefix, exitNextHop, exitIntf); err != nil {
|
if err := addToRouteTable(prefix, exitNextHop, exitIntf); err != nil {
|
||||||
return netip.Addr{}, "", fmt.Errorf("add route to table: %w", err)
|
return netip.Addr{}, nil, fmt.Errorf("add route to table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitNextHop, exitIntf, nil
|
return exitNextHop, exitIntf, nil
|
||||||
@@ -208,7 +194,7 @@ func addRouteToNonVPNIntf(
|
|||||||
|
|
||||||
// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix
|
// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix
|
||||||
// in two /1 prefixes to avoid replacing the existing default route
|
// in two /1 prefixes to avoid replacing the existing default route
|
||||||
func genericAddVPNRoute(prefix netip.Prefix, intf string) error {
|
func genericAddVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
if prefix == defaultv4 {
|
if prefix == defaultv4 {
|
||||||
if err := addToRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
if err := addToRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -250,7 +236,7 @@ func genericAddVPNRoute(prefix netip.Prefix, intf string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table
|
// addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table
|
||||||
func addNonExistingRoute(prefix netip.Prefix, intf string) error {
|
func addNonExistingRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
ok, err := existsInRouteTable(prefix)
|
ok, err := existsInRouteTable(prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("exists in route table: %w", err)
|
return fmt.Errorf("exists in route table: %w", err)
|
||||||
@@ -277,7 +263,7 @@ func addNonExistingRoute(prefix netip.Prefix, intf string) error {
|
|||||||
|
|
||||||
// genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given,
|
// genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given,
|
||||||
// it will remove the split /1 prefixes
|
// it will remove the split /1 prefixes
|
||||||
func genericRemoveVPNRoute(prefix netip.Prefix, intf string) error {
|
func genericRemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
if prefix == defaultv4 {
|
if prefix == defaultv4 {
|
||||||
var result *multierror.Error
|
var result *multierror.Error
|
||||||
if err := removeFromRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
if err := removeFromRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
||||||
@@ -333,17 +319,17 @@ func getPrefixFromIP(ip net.IP) (*netip.Prefix, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupRoutingWithRouteManager(routeManager **RouteManager, initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
func setupRoutingWithRouteManager(routeManager **RouteManager, initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
initialNextHopV4, initialIntfV4, err := getNextHop(netip.IPv4Unspecified())
|
initialNextHopV4, initialIntfV4, err := GetNextHop(netip.IPv4Unspecified())
|
||||||
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
log.Errorf("Unable to get initial v4 default next hop: %v", err)
|
log.Errorf("Unable to get initial v4 default next hop: %v", err)
|
||||||
}
|
}
|
||||||
initialNextHopV6, initialIntfV6, err := getNextHop(netip.IPv6Unspecified())
|
initialNextHopV6, initialIntfV6, err := GetNextHop(netip.IPv6Unspecified())
|
||||||
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
||||||
log.Errorf("Unable to get initial v6 default next hop: %v", err)
|
log.Errorf("Unable to get initial v6 default next hop: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
*routeManager = NewRouteManager(
|
*routeManager = NewRouteManager(
|
||||||
func(prefix netip.Prefix) (netip.Addr, string, error) {
|
func(prefix netip.Prefix) (netip.Addr, *net.Interface, error) {
|
||||||
addr := prefix.Addr()
|
addr := prefix.Addr()
|
||||||
nexthop, intf := initialNextHopV4, initialIntfV4
|
nexthop, intf := initialNextHopV4, initialIntfV4
|
||||||
if addr.Is6() {
|
if addr.Is6() {
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ func enableIPForwarding() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVPNRoute(netip.Prefix, string) error {
|
func addVPNRoute(netip.Prefix, *net.Interface) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeVPNRoute(netip.Prefix, string) error {
|
func removeVPNRoute(netip.Prefix, *net.Interface) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,39 +3,35 @@
|
|||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// selected BSD Route flags.
|
type Route struct {
|
||||||
const (
|
Dst netip.Prefix
|
||||||
RTF_UP = 0x1
|
Gw netip.Addr
|
||||||
RTF_GATEWAY = 0x2
|
Interface *net.Interface
|
||||||
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 getRoutesFromTable() ([]netip.Prefix, error) {
|
func getRoutesFromTable() ([]netip.Prefix, error) {
|
||||||
tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0)
|
tab, err := retryFetchRIB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("fetch RIB: %v", err)
|
||||||
}
|
}
|
||||||
msgs, err := route.ParseRIB(route.RIBTypeRoute, tab)
|
msgs, err := route.ParseRIB(route.RIBTypeRoute, tab)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("parse RIB: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixList []netip.Prefix
|
var prefixList []netip.Prefix
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
m := msg.(*route.RouteMessage)
|
m := msg.(*route.RouteMessage)
|
||||||
@@ -43,58 +39,121 @@ func getRoutesFromTable() ([]netip.Prefix, error) {
|
|||||||
if m.Version < 3 || m.Version > 5 {
|
if m.Version < 3 || m.Version > 5 {
|
||||||
return nil, fmt.Errorf("unexpected RIB message version: %d", m.Version)
|
return nil, fmt.Errorf("unexpected RIB message version: %d", m.Version)
|
||||||
}
|
}
|
||||||
if m.Type != 4 /* RTM_GET */ {
|
if m.Type != syscall.RTM_GET {
|
||||||
return nil, fmt.Errorf("unexpected RIB message type: %d", m.Type)
|
return nil, fmt.Errorf("unexpected RIB message type: %d", m.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Flags&RTF_UP == 0 ||
|
if m.Flags&syscall.RTF_UP == 0 ||
|
||||||
m.Flags&(RTF_REJECT|RTF_BLACKHOLE) != 0 {
|
m.Flags&(syscall.RTF_REJECT|syscall.RTF_BLACKHOLE|syscall.RTF_WASCLONED) != 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.Addrs) < 3 {
|
route, err := MsgToRoute(m)
|
||||||
log.Warnf("Unexpected RIB message Addrs: %v", m.Addrs)
|
if err != nil {
|
||||||
|
log.Warnf("Failed to parse route message: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if route.Dst.IsValid() {
|
||||||
addr, ok := toNetIPAddr(m.Addrs[0])
|
prefixList = append(prefixList, route.Dst)
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cidr := 32
|
|
||||||
if mask := m.Addrs[2]; mask != nil {
|
|
||||||
cidr, ok = toCIDR(mask)
|
|
||||||
if !ok {
|
|
||||||
log.Debugf("Unexpected RIB message Addrs[2]: %v", mask)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
routePrefix := netip.PrefixFrom(addr, cidr)
|
|
||||||
if routePrefix.IsValid() {
|
|
||||||
prefixList = append(prefixList, routePrefix)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return prefixList, nil
|
return prefixList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNetIPAddr(a route.Addr) (netip.Addr, bool) {
|
func retryFetchRIB() ([]byte, error) {
|
||||||
|
var out []byte
|
||||||
|
operation := func() error {
|
||||||
|
var err error
|
||||||
|
out, err = route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0)
|
||||||
|
if errors.Is(err, syscall.ENOMEM) {
|
||||||
|
log.Debug("~etrying fetchRIB due to 'cannot allocate memory' error")
|
||||||
|
return err
|
||||||
|
} else if err != nil {
|
||||||
|
return backoff.Permanent(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expBackOff := backoff.NewExponentialBackOff()
|
||||||
|
expBackOff.InitialInterval = 50 * time.Millisecond
|
||||||
|
expBackOff.MaxInterval = 500 * time.Millisecond
|
||||||
|
expBackOff.MaxElapsedTime = 1 * time.Second
|
||||||
|
|
||||||
|
err := backoff.Retry(operation, expBackOff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch routing information: %w", err)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNetIP(a route.Addr) netip.Addr {
|
||||||
switch t := a.(type) {
|
switch t := a.(type) {
|
||||||
case *route.Inet4Addr:
|
case *route.Inet4Addr:
|
||||||
return netip.AddrFrom4(t.IP), true
|
return netip.AddrFrom4(t.IP)
|
||||||
|
case *route.Inet6Addr:
|
||||||
|
ip := netip.AddrFrom16(t.IP)
|
||||||
|
if t.ZoneID != 0 {
|
||||||
|
ip.WithZone(strconv.Itoa(t.ZoneID))
|
||||||
|
}
|
||||||
|
return ip
|
||||||
default:
|
default:
|
||||||
return netip.Addr{}, false
|
return netip.Addr{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCIDR(a route.Addr) (int, bool) {
|
func ones(a route.Addr) (int, error) {
|
||||||
switch t := a.(type) {
|
switch t := a.(type) {
|
||||||
case *route.Inet4Addr:
|
case *route.Inet4Addr:
|
||||||
mask := net.IPv4Mask(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
|
mask, _ := net.IPMask(t.IP[:]).Size()
|
||||||
cidr, _ := mask.Size()
|
return mask, nil
|
||||||
return cidr, true
|
case *route.Inet6Addr:
|
||||||
|
mask, _ := net.IPMask(t.IP[:]).Size()
|
||||||
|
return mask, nil
|
||||||
default:
|
default:
|
||||||
return 0, false
|
return 0, fmt.Errorf("unexpected address type: %T", a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MsgToRoute(msg *route.RouteMessage) (*Route, error) {
|
||||||
|
dstIP, nexthop, dstMask := msg.Addrs[0], msg.Addrs[1], msg.Addrs[2]
|
||||||
|
|
||||||
|
addr := toNetIP(dstIP)
|
||||||
|
|
||||||
|
var nexthopAddr netip.Addr
|
||||||
|
var nexthopIntf *net.Interface
|
||||||
|
|
||||||
|
switch t := nexthop.(type) {
|
||||||
|
case *route.Inet4Addr, *route.Inet6Addr:
|
||||||
|
nexthopAddr = toNetIP(t)
|
||||||
|
case *route.LinkAddr:
|
||||||
|
nexthopIntf = &net.Interface{
|
||||||
|
Index: t.Index,
|
||||||
|
Name: t.Name,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected next hop type: %T", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix netip.Prefix
|
||||||
|
|
||||||
|
if dstMask == nil {
|
||||||
|
if addr.Is4() {
|
||||||
|
prefix = netip.PrefixFrom(addr, 32)
|
||||||
|
} else {
|
||||||
|
prefix = netip.PrefixFrom(addr, 128)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bits, err := ones(dstMask)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse mask: %v", dstMask)
|
||||||
|
}
|
||||||
|
prefix = netip.PrefixFrom(addr, bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Route{
|
||||||
|
Dst: prefix,
|
||||||
|
Gw: nexthopAddr,
|
||||||
|
Interface: nexthopIntf,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
57
client/internal/routemanager/systemops_bsd_test.go
Normal file
57
client/internal/routemanager/systemops_bsd_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/net/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBits(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr route.Addr
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "IPv4 all ones",
|
||||||
|
addr: &route.Inet4Addr{IP: [4]byte{255, 255, 255, 255}},
|
||||||
|
want: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 normal mask",
|
||||||
|
addr: &route.Inet4Addr{IP: [4]byte{255, 255, 255, 0}},
|
||||||
|
want: 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 all ones",
|
||||||
|
addr: &route.Inet6Addr{IP: [16]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
||||||
|
want: 128,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 normal mask",
|
||||||
|
addr: &route.Inet6Addr{IP: [16]byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||||
|
want: 64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unsupported type",
|
||||||
|
addr: &route.LinkAddr{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := ones(tt.addr)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,15 +27,15 @@ func cleanupRouting() error {
|
|||||||
return cleanupRoutingWithRouteManager(routeManager)
|
return cleanupRoutingWithRouteManager(routeManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
return routeCmd("add", prefix, nexthop, intf)
|
return routeCmd("add", prefix, nexthop, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
return routeCmd("delete", prefix, nexthop, intf)
|
return routeCmd("delete", prefix, nexthop, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeCmd(action string, prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
func routeCmd(action string, prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
inet := "-inet"
|
inet := "-inet"
|
||||||
network := prefix.String()
|
network := prefix.String()
|
||||||
if prefix.IsSingleIP() {
|
if prefix.IsSingleIP() {
|
||||||
@@ -43,18 +43,13 @@ func routeCmd(action string, prefix netip.Prefix, nexthop netip.Addr, intf strin
|
|||||||
}
|
}
|
||||||
if prefix.Addr().Is6() {
|
if prefix.Addr().Is6() {
|
||||||
inet = "-inet6"
|
inet = "-inet6"
|
||||||
// Special case for IPv6 split default route, pointing to the wg interface fails
|
|
||||||
// TODO: Remove once we have IPv6 support on the interface
|
|
||||||
if prefix.Bits() == 1 {
|
|
||||||
intf = "lo0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"-n", action, inet, network}
|
args := []string{"-n", action, inet, network}
|
||||||
if nexthop.IsValid() {
|
if nexthop.IsValid() {
|
||||||
args = append(args, nexthop.Unmap().String())
|
args = append(args, nexthop.Unmap().String())
|
||||||
} else if intf != "" {
|
} else if intf != nil {
|
||||||
args = append(args, "-interface", intf)
|
args = append(args, "-interface", intf.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := retryRouteCmd(args); err != nil {
|
if err := retryRouteCmd(args); err != nil {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func init() {
|
|||||||
|
|
||||||
func TestConcurrentRoutes(t *testing.T) {
|
func TestConcurrentRoutes(t *testing.T) {
|
||||||
baseIP := netip.MustParseAddr("192.0.2.0")
|
baseIP := netip.MustParseAddr("192.0.2.0")
|
||||||
intf := "lo0"
|
intf := &net.Interface{Name: "lo0"}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < 1024; i++ {
|
for i := 0; i < 1024; i++ {
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ func enableIPForwarding() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVPNRoute(netip.Prefix, string) error {
|
func addVPNRoute(netip.Prefix, *net.Interface) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeVPNRoute(netip.Prefix, string) error {
|
func removeVPNRoute(netip.Prefix, *net.Interface) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ var routeManager = &RouteManager{}
|
|||||||
// originalSysctl stores the original sysctl values before they are modified
|
// originalSysctl stores the original sysctl values before they are modified
|
||||||
var originalSysctl map[string]int
|
var originalSysctl map[string]int
|
||||||
|
|
||||||
// determines whether to use the legacy routing setup
|
|
||||||
var isLegacy = os.Getenv("NB_USE_LEGACY_ROUTING") == "true" || nbnet.CustomRoutingDisabled()
|
|
||||||
|
|
||||||
// sysctlFailed is used as an indicator to emit a warning when default routes are configured
|
// sysctlFailed is used as an indicator to emit a warning when default routes are configured
|
||||||
var sysctlFailed bool
|
var sysctlFailed bool
|
||||||
|
|
||||||
@@ -62,6 +59,20 @@ type ruleParams struct {
|
|||||||
description string
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isLegacy determines whether to use the legacy routing setup
|
||||||
|
func isLegacy() bool {
|
||||||
|
return os.Getenv("NB_USE_LEGACY_ROUTING") == "true" || nbnet.CustomRoutingDisabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setIsLegacy sets the legacy routing setup
|
||||||
|
func setIsLegacy(b bool) {
|
||||||
|
if b {
|
||||||
|
os.Setenv("NB_USE_LEGACY_ROUTING", "true")
|
||||||
|
} else {
|
||||||
|
os.Unsetenv("NB_USE_LEGACY_ROUTING")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getSetupRules() []ruleParams {
|
func getSetupRules() []ruleParams {
|
||||||
return []ruleParams{
|
return []ruleParams{
|
||||||
{100, -1, syscall.RT_TABLE_MAIN, netlink.FAMILY_V4, false, 0, "rule with suppress prefixlen v4"},
|
{100, -1, syscall.RT_TABLE_MAIN, netlink.FAMILY_V4, false, 0, "rule with suppress prefixlen v4"},
|
||||||
@@ -82,7 +93,7 @@ func getSetupRules() []ruleParams {
|
|||||||
// This table is where a default route or other specific routes received from the management server are configured,
|
// This table is where a default route or other specific routes received from the management server are configured,
|
||||||
// enabling VPN connectivity.
|
// enabling VPN connectivity.
|
||||||
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) {
|
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) {
|
||||||
if isLegacy {
|
if isLegacy() {
|
||||||
log.Infof("Using legacy routing setup")
|
log.Infof("Using legacy routing setup")
|
||||||
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
||||||
}
|
}
|
||||||
@@ -111,7 +122,7 @@ func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.Before
|
|||||||
if err := addRule(rule); err != nil {
|
if err := addRule(rule); err != nil {
|
||||||
if errors.Is(err, syscall.EOPNOTSUPP) {
|
if errors.Is(err, syscall.EOPNOTSUPP) {
|
||||||
log.Warnf("Rule operations are not supported, falling back to the legacy routing setup")
|
log.Warnf("Rule operations are not supported, falling back to the legacy routing setup")
|
||||||
isLegacy = true
|
setIsLegacy(true)
|
||||||
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
||||||
}
|
}
|
||||||
return nil, nil, fmt.Errorf("%s: %w", rule.description, err)
|
return nil, nil, fmt.Errorf("%s: %w", rule.description, err)
|
||||||
@@ -125,7 +136,7 @@ func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.Before
|
|||||||
// It systematically removes the three rules and any associated routing table entries to ensure a clean state.
|
// It systematically removes the three rules and any associated routing table entries to ensure a clean state.
|
||||||
// The function uses error aggregation to report any errors encountered during the cleanup process.
|
// The function uses error aggregation to report any errors encountered during the cleanup process.
|
||||||
func cleanupRouting() error {
|
func cleanupRouting() error {
|
||||||
if isLegacy {
|
if isLegacy() {
|
||||||
return cleanupRoutingWithRouteManager(routeManager)
|
return cleanupRoutingWithRouteManager(routeManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,16 +165,16 @@ func cleanupRouting() error {
|
|||||||
return result.ErrorOrNil()
|
return result.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
return addRoute(prefix, nexthop, intf, syscall.RT_TABLE_MAIN)
|
return addRoute(prefix, nexthop, intf, syscall.RT_TABLE_MAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
return removeRoute(prefix, nexthop, intf, syscall.RT_TABLE_MAIN)
|
return removeRoute(prefix, nexthop, intf, syscall.RT_TABLE_MAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVPNRoute(prefix netip.Prefix, intf string) error {
|
func addVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
if isLegacy {
|
if isLegacy() {
|
||||||
return genericAddVPNRoute(prefix, intf)
|
return genericAddVPNRoute(prefix, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +196,8 @@ func addVPNRoute(prefix netip.Prefix, intf string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeVPNRoute(prefix netip.Prefix, intf string) error {
|
func removeVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
if isLegacy {
|
if isLegacy() {
|
||||||
return genericRemoveVPNRoute(prefix, intf)
|
return genericRemoveVPNRoute(prefix, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +255,7 @@ func getRoutes(tableID, family int) ([]netip.Prefix, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addRoute adds a route to a specific routing table identified by tableID.
|
// addRoute adds a route to a specific routing table identified by tableID.
|
||||||
func addRoute(prefix netip.Prefix, addr netip.Addr, intf string, tableID int) error {
|
func addRoute(prefix netip.Prefix, addr netip.Addr, intf *net.Interface, tableID int) error {
|
||||||
route := &netlink.Route{
|
route := &netlink.Route{
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
Table: tableID,
|
Table: tableID,
|
||||||
@@ -304,7 +315,10 @@ func removeUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
|||||||
Dst: ipNet,
|
Dst: ipNet,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
if err := netlink.RouteDel(route); err != nil &&
|
||||||
|
!errors.Is(err, syscall.ESRCH) &&
|
||||||
|
!errors.Is(err, syscall.ENOENT) &&
|
||||||
|
!errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
return fmt.Errorf("netlink remove unreachable route: %w", err)
|
return fmt.Errorf("netlink remove unreachable route: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +327,7 @@ func removeUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// removeRoute removes a route from a specific routing table identified by tableID.
|
// removeRoute removes a route from a specific routing table identified by tableID.
|
||||||
func removeRoute(prefix netip.Prefix, addr netip.Addr, intf string, tableID int) error {
|
func removeRoute(prefix netip.Prefix, addr netip.Addr, intf *net.Interface, tableID int) error {
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
return fmt.Errorf("parse prefix %s: %w", prefix, err)
|
||||||
@@ -467,20 +481,22 @@ func removeRule(params ruleParams) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addNextHop adds the gateway and device to the route.
|
// addNextHop adds the gateway and device to the route.
|
||||||
func addNextHop(addr netip.Addr, intf string, route *netlink.Route) error {
|
func addNextHop(addr netip.Addr, intf *net.Interface, route *netlink.Route) error {
|
||||||
if addr.IsValid() {
|
if intf != nil {
|
||||||
route.Gw = addr.AsSlice()
|
route.LinkIndex = intf.Index
|
||||||
if intf == "" {
|
|
||||||
intf = addr.Zone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if intf != "" {
|
if addr.IsValid() {
|
||||||
link, err := netlink.LinkByName(intf)
|
route.Gw = addr.AsSlice()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("set interface %s: %w", intf, err)
|
// if zone is set, it means the gateway is a link-local address, so we set the link index
|
||||||
|
if addr.Zone() != "" && intf == nil {
|
||||||
|
link, err := netlink.LinkByName(addr.Zone())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get link by name for zone %s: %w", addr.Zone(), err)
|
||||||
|
}
|
||||||
|
route.LinkIndex = link.Attrs().Index
|
||||||
}
|
}
|
||||||
route.LinkIndex = link.Attrs().Index
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
@@ -14,10 +15,10 @@ func enableIPForwarding() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVPNRoute(prefix netip.Prefix, intf string) error {
|
func addVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
return genericAddVPNRoute(prefix, intf)
|
return genericAddVPNRoute(prefix, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeVPNRoute(prefix netip.Prefix, intf string) error {
|
func removeVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
||||||
return genericRemoveVPNRoute(prefix, intf)
|
return genericRemoveVPNRoute(prefix, intf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
|
|
||||||
for n, testCase := range testCases {
|
for n, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Setenv("NB_DISABLE_ROUTE_CACHE", "true")
|
||||||
|
|
||||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -67,7 +69,11 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
assert.NoError(t, cleanupRouting())
|
assert.NoError(t, cleanupRouting())
|
||||||
})
|
})
|
||||||
|
|
||||||
err = genericAddVPNRoute(testCase.prefix, wgInterface.Name())
|
index, err := net.InterfaceByName(wgInterface.Name())
|
||||||
|
require.NoError(t, err, "InterfaceByName should not return err")
|
||||||
|
intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()}
|
||||||
|
|
||||||
|
err = addVPNRoute(testCase.prefix, intf)
|
||||||
require.NoError(t, err, "genericAddVPNRoute should not return err")
|
require.NoError(t, err, "genericAddVPNRoute should not return err")
|
||||||
|
|
||||||
if testCase.shouldRouteToWireguard {
|
if testCase.shouldRouteToWireguard {
|
||||||
@@ -78,13 +84,13 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
exists, err := existsInRouteTable(testCase.prefix)
|
exists, err := existsInRouteTable(testCase.prefix)
|
||||||
require.NoError(t, err, "existsInRouteTable should not return err")
|
require.NoError(t, err, "existsInRouteTable should not return err")
|
||||||
if exists && testCase.shouldRouteToWireguard {
|
if exists && testCase.shouldRouteToWireguard {
|
||||||
err = genericRemoveVPNRoute(testCase.prefix, wgInterface.Name())
|
err = removeVPNRoute(testCase.prefix, intf)
|
||||||
require.NoError(t, err, "genericRemoveVPNRoute should not return err")
|
require.NoError(t, err, "genericRemoveVPNRoute should not return err")
|
||||||
|
|
||||||
prefixGateway, _, err := getNextHop(testCase.prefix.Addr())
|
prefixGateway, _, err := GetNextHop(testCase.prefix.Addr())
|
||||||
require.NoError(t, err, "getNextHop should not return err")
|
require.NoError(t, err, "GetNextHop should not return err")
|
||||||
|
|
||||||
internetGateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
|
internetGateway, _, err := GetNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if testCase.shouldBeRemoved {
|
if testCase.shouldBeRemoved {
|
||||||
@@ -98,7 +104,7 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetNextHop(t *testing.T) {
|
func TestGetNextHop(t *testing.T) {
|
||||||
gateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
|
gateway, _, err := GetNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
}
|
}
|
||||||
@@ -124,7 +130,7 @@ func TestGetNextHop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localIP, _, err := getNextHop(testingPrefix.Addr())
|
localIP, _, err := GetNextHop(testingPrefix.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error: ", err)
|
t.Fatal("shouldn't return error: ", err)
|
||||||
}
|
}
|
||||||
@@ -140,7 +146,7 @@ func TestGetNextHop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddExistAndRemoveRoute(t *testing.T) {
|
func TestAddExistAndRemoveRoute(t *testing.T) {
|
||||||
defaultGateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
|
defaultGateway, _, err := GetNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
t.Log("defaultGateway: ", defaultGateway)
|
t.Log("defaultGateway: ", defaultGateway)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
@@ -182,12 +188,16 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for n, testCase := range testCases {
|
for n, testCase := range testCases {
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
log.SetOutput(&buf)
|
log.SetOutput(&buf)
|
||||||
defer func() {
|
defer func() {
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
}()
|
}()
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Setenv("NB_USE_LEGACY_ROUTING", "true")
|
||||||
|
t.Setenv("NB_DISABLE_ROUTE_CACHE", "true")
|
||||||
|
|
||||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -200,14 +210,18 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
err = wgInterface.Create()
|
err = wgInterface.Create()
|
||||||
require.NoError(t, err, "should create testing wireguard interface")
|
require.NoError(t, err, "should create testing wireguard interface")
|
||||||
|
|
||||||
|
index, err := net.InterfaceByName(wgInterface.Name())
|
||||||
|
require.NoError(t, err, "InterfaceByName should not return err")
|
||||||
|
intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()}
|
||||||
|
|
||||||
// Prepare the environment
|
// Prepare the environment
|
||||||
if testCase.preExistingPrefix.IsValid() {
|
if testCase.preExistingPrefix.IsValid() {
|
||||||
err := genericAddVPNRoute(testCase.preExistingPrefix, wgInterface.Name())
|
err := addVPNRoute(testCase.preExistingPrefix, intf)
|
||||||
require.NoError(t, err, "should not return err when adding pre-existing route")
|
require.NoError(t, err, "should not return err when adding pre-existing route")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the route
|
// Add the route
|
||||||
err = genericAddVPNRoute(testCase.prefix, wgInterface.Name())
|
err = addVPNRoute(testCase.prefix, intf)
|
||||||
require.NoError(t, err, "should not return err when adding route")
|
require.NoError(t, err, "should not return err when adding route")
|
||||||
|
|
||||||
if testCase.shouldAddRoute {
|
if testCase.shouldAddRoute {
|
||||||
@@ -217,7 +231,7 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
require.True(t, ok, "route should exist")
|
require.True(t, ok, "route should exist")
|
||||||
|
|
||||||
// remove route again if added
|
// remove route again if added
|
||||||
err = genericRemoveVPNRoute(testCase.prefix, wgInterface.Name())
|
err = removeVPNRoute(testCase.prefix, intf)
|
||||||
require.NoError(t, err, "should not return err")
|
require.NoError(t, err, "should not return err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,43 +359,47 @@ func setupTestEnv(t *testing.T) {
|
|||||||
assert.NoError(t, cleanupRouting())
|
assert.NoError(t, cleanupRouting())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
index, err := net.InterfaceByName(wgIface.Name())
|
||||||
|
require.NoError(t, err, "InterfaceByName should not return err")
|
||||||
|
intf := &net.Interface{Index: index.Index, Name: wgIface.Name()}
|
||||||
|
|
||||||
// default route exists in main table and vpn table
|
// default route exists in main table and vpn table
|
||||||
err = addVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), wgIface.Name())
|
err = addVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf)
|
||||||
require.NoError(t, err, "addVPNRoute should not return err")
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err = removeVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), wgIface.Name())
|
err = removeVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf)
|
||||||
assert.NoError(t, err, "removeVPNRoute should not return err")
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
})
|
})
|
||||||
|
|
||||||
// 10.0.0.0/8 route exists in main table and vpn table
|
// 10.0.0.0/8 route exists in main table and vpn table
|
||||||
err = addVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Name())
|
err = addVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf)
|
||||||
require.NoError(t, err, "addVPNRoute should not return err")
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err = removeVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Name())
|
err = removeVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf)
|
||||||
assert.NoError(t, err, "removeVPNRoute should not return err")
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
})
|
})
|
||||||
|
|
||||||
// 10.10.0.0/24 more specific route exists in vpn table
|
// 10.10.0.0/24 more specific route exists in vpn table
|
||||||
err = addVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), wgIface.Name())
|
err = addVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf)
|
||||||
require.NoError(t, err, "addVPNRoute should not return err")
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err = removeVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), wgIface.Name())
|
err = removeVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf)
|
||||||
assert.NoError(t, err, "removeVPNRoute should not return err")
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
})
|
})
|
||||||
|
|
||||||
// 127.0.10.0/24 more specific route exists in vpn table
|
// 127.0.10.0/24 more specific route exists in vpn table
|
||||||
err = addVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), wgIface.Name())
|
err = addVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf)
|
||||||
require.NoError(t, err, "addVPNRoute should not return err")
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err = removeVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), wgIface.Name())
|
err = removeVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf)
|
||||||
assert.NoError(t, err, "removeVPNRoute should not return err")
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
})
|
})
|
||||||
|
|
||||||
// unique route in vpn table
|
// unique route in vpn table
|
||||||
err = addVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), wgIface.Name())
|
err = addVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf)
|
||||||
require.NoError(t, err, "addVPNRoute should not return err")
|
require.NoError(t, err, "addVPNRoute should not return err")
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err = removeVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), wgIface.Name())
|
err = removeVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf)
|
||||||
assert.NoError(t, err, "removeVPNRoute should not return err")
|
assert.NoError(t, err, "removeVPNRoute should not return err")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -392,8 +410,8 @@ func assertWGOutInterface(t *testing.T, prefix netip.Prefix, wgIface *iface.WGIf
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixGateway, _, err := getNextHop(prefix.Addr())
|
prefixGateway, _, err := GetNextHop(prefix.Addr())
|
||||||
require.NoError(t, err, "getNextHop should not return err")
|
require.NoError(t, err, "GetNextHop should not return err")
|
||||||
if invert {
|
if invert {
|
||||||
assert.NotEqual(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should not point to wireguard interface IP")
|
assert.NotEqual(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should not point to wireguard interface IP")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,21 +6,57 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/yusufpapurcu/wmi"
|
"github.com/yusufpapurcu/wmi"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Win32_IP4RouteTable struct {
|
type MSFT_NetRoute struct {
|
||||||
Destination string
|
DestinationPrefix string
|
||||||
Mask string
|
NextHop string
|
||||||
|
InterfaceIndex int32
|
||||||
|
InterfaceAlias string
|
||||||
|
AddressFamily uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Destination netip.Prefix
|
||||||
|
Nexthop netip.Addr
|
||||||
|
Interface *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
type MSFT_NetNeighbor struct {
|
||||||
|
IPAddress string
|
||||||
|
LinkLayerAddress string
|
||||||
|
State uint8
|
||||||
|
AddressFamily uint16
|
||||||
|
InterfaceIndex uint32
|
||||||
|
InterfaceAlias string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Neighbor struct {
|
||||||
|
IPAddress netip.Addr
|
||||||
|
LinkLayerAddress string
|
||||||
|
State uint8
|
||||||
|
AddressFamily uint16
|
||||||
|
InterfaceIndex uint32
|
||||||
|
InterfaceAlias string
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefixList []netip.Prefix
|
||||||
|
var lastUpdate time.Time
|
||||||
|
var mux = sync.Mutex{}
|
||||||
|
|
||||||
var routeManager *RouteManager
|
var routeManager *RouteManager
|
||||||
|
|
||||||
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
||||||
@@ -32,82 +68,115 @@ func cleanupRouting() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getRoutesFromTable() ([]netip.Prefix, error) {
|
func getRoutesFromTable() ([]netip.Prefix, error) {
|
||||||
var routes []Win32_IP4RouteTable
|
mux.Lock()
|
||||||
query := "SELECT Destination, Mask FROM Win32_IP4RouteTable"
|
defer mux.Unlock()
|
||||||
|
|
||||||
err := wmi.Query(query, &routes)
|
// If many routes are added at the same time this might block for a long time (seconds to minutes), so we cache the result
|
||||||
|
if !isCacheDisabled() && time.Since(lastUpdate) < 2*time.Second {
|
||||||
|
return prefixList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := GetRoutes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get routes: %w", err)
|
return nil, fmt.Errorf("get routes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixList []netip.Prefix
|
prefixList = nil
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
addr, err := netip.ParseAddr(route.Destination)
|
prefixList = append(prefixList, route.Destination)
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Unable to parse route destination %s: %v", route.Destination, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
maskSlice := net.ParseIP(route.Mask).To4()
|
|
||||||
if maskSlice == nil {
|
|
||||||
log.Warnf("Unable to parse route mask %s", route.Mask)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
mask := net.IPv4Mask(maskSlice[0], maskSlice[1], maskSlice[2], maskSlice[3])
|
|
||||||
cidr, _ := mask.Size()
|
|
||||||
|
|
||||||
routePrefix := netip.PrefixFrom(addr, cidr)
|
|
||||||
if routePrefix.IsValid() && routePrefix.Addr().Is4() {
|
|
||||||
prefixList = append(prefixList, routePrefix)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastUpdate = time.Now()
|
||||||
return prefixList, nil
|
return prefixList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRoutePowershell(prefix netip.Prefix, nexthop netip.Addr, intf, intfIdx string) error {
|
func GetRoutes() ([]Route, error) {
|
||||||
destinationPrefix := prefix.String()
|
var entries []MSFT_NetRoute
|
||||||
psCmd := "New-NetRoute"
|
|
||||||
|
|
||||||
addressFamily := "IPv4"
|
query := `SELECT DestinationPrefix, NextHop, InterfaceIndex, InterfaceAlias, AddressFamily FROM MSFT_NetRoute`
|
||||||
if prefix.Addr().Is6() {
|
if err := wmi.QueryNamespace(query, &entries, `ROOT\StandardCimv2`); err != nil {
|
||||||
addressFamily = "IPv6"
|
return nil, fmt.Errorf("get routes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
script := fmt.Sprintf(
|
var routes []Route
|
||||||
`%s -AddressFamily "%s" -DestinationPrefix "%s" -Confirm:$False -ErrorAction Stop -PolicyStore ActiveStore`,
|
for _, entry := range entries {
|
||||||
psCmd, addressFamily, destinationPrefix,
|
dest, err := netip.ParsePrefix(entry.DestinationPrefix)
|
||||||
)
|
if err != nil {
|
||||||
|
log.Warnf("Unable to parse route destination %s: %v", entry.DestinationPrefix, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if intfIdx != "" {
|
nexthop, err := netip.ParseAddr(entry.NextHop)
|
||||||
script = fmt.Sprintf(
|
if err != nil {
|
||||||
`%s -InterfaceIndex %s`, script, intfIdx,
|
log.Warnf("Unable to parse route next hop %s: %v", entry.NextHop, err)
|
||||||
)
|
continue
|
||||||
} else {
|
}
|
||||||
script = fmt.Sprintf(
|
|
||||||
`%s -InterfaceAlias "%s"`, script, intf,
|
var intf *net.Interface
|
||||||
)
|
if entry.InterfaceIndex != 0 {
|
||||||
|
intf = &net.Interface{
|
||||||
|
Index: int(entry.InterfaceIndex),
|
||||||
|
Name: entry.InterfaceAlias,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routes = append(routes, Route{
|
||||||
|
Destination: dest,
|
||||||
|
Nexthop: nexthop,
|
||||||
|
Interface: intf,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if nexthop.IsValid() {
|
return routes, nil
|
||||||
script = fmt.Sprintf(
|
|
||||||
`%s -NextHop "%s"`, script, nexthop,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := exec.Command("powershell", "-Command", script).CombinedOutput()
|
|
||||||
log.Tracef("PowerShell %s: %s", script, string(out))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("PowerShell add route: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRouteCmd(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
func GetNeighbors() ([]Neighbor, error) {
|
||||||
args := []string{"add", prefix.String(), nexthop.Unmap().String()}
|
var entries []MSFT_NetNeighbor
|
||||||
|
query := `SELECT IPAddress, LinkLayerAddress, State, AddressFamily, InterfaceIndex, InterfaceAlias FROM MSFT_NetNeighbor`
|
||||||
|
if err := wmi.QueryNamespace(query, &entries, `ROOT\StandardCimv2`); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query MSFT_NetNeighbor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
out, err := exec.Command("route", args...).CombinedOutput()
|
var neighbors []Neighbor
|
||||||
|
for _, entry := range entries {
|
||||||
|
addr, err := netip.ParseAddr(entry.IPAddress)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to parse neighbor IP address %s: %v", entry.IPAddress, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
neighbors = append(neighbors, Neighbor{
|
||||||
|
IPAddress: addr,
|
||||||
|
LinkLayerAddress: entry.LinkLayerAddress,
|
||||||
|
State: entry.State,
|
||||||
|
AddressFamily: entry.AddressFamily,
|
||||||
|
InterfaceIndex: entry.InterfaceIndex,
|
||||||
|
InterfaceAlias: entry.InterfaceAlias,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRouteCmd(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
|
args := []string{"add", prefix.String()}
|
||||||
|
|
||||||
|
if nexthop.IsValid() {
|
||||||
|
args = append(args, nexthop.Unmap().String())
|
||||||
|
} else {
|
||||||
|
addr := "0.0.0.0"
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
addr = "::"
|
||||||
|
}
|
||||||
|
args = append(args, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if intf != nil {
|
||||||
|
args = append(args, "if", strconv.Itoa(intf.Index))
|
||||||
|
}
|
||||||
|
|
||||||
|
routeCmd := uspfilter.GetSystem32Command("route")
|
||||||
|
|
||||||
|
out, err := exec.Command(routeCmd, args...).CombinedOutput()
|
||||||
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("route add: %w", err)
|
return fmt.Errorf("route add: %w", err)
|
||||||
@@ -116,28 +185,29 @@ func addRouteCmd(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf *net.Interface) error {
|
||||||
var intfIdx string
|
if nexthop.Zone() != "" && intf == nil {
|
||||||
if nexthop.Zone() != "" {
|
zone, err := strconv.Atoi(nexthop.Zone())
|
||||||
intfIdx = nexthop.Zone()
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid zone: %w", err)
|
||||||
|
}
|
||||||
|
intf = &net.Interface{Index: zone}
|
||||||
nexthop.WithZone("")
|
nexthop.WithZone("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Powershell doesn't support adding routes without an interface but allows to add interface by name
|
|
||||||
if intf != "" || intfIdx != "" {
|
|
||||||
return addRoutePowershell(prefix, nexthop, intf, intfIdx)
|
|
||||||
}
|
|
||||||
return addRouteCmd(prefix, nexthop, intf)
|
return addRouteCmd(prefix, nexthop, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, _ *net.Interface) error {
|
||||||
args := []string{"delete", prefix.String()}
|
args := []string{"delete", prefix.String()}
|
||||||
if nexthop.IsValid() {
|
if nexthop.IsValid() {
|
||||||
nexthop.WithZone("")
|
nexthop.WithZone("")
|
||||||
args = append(args, nexthop.Unmap().String())
|
args = append(args, nexthop.Unmap().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := exec.Command("route", args...).CombinedOutput()
|
routeCmd := uspfilter.GetSystem32Command("route")
|
||||||
|
|
||||||
|
out, err := exec.Command(routeCmd, args...).CombinedOutput()
|
||||||
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -145,3 +215,7 @@ func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, _ string) err
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isCacheDisabled() bool {
|
||||||
|
return os.Getenv("NB_DISABLE_ROUTE_CACHE") == "true"
|
||||||
|
}
|
||||||
|
|||||||
128
client/internal/routeselector/routeselector.go
Normal file
128
client/internal/routeselector/routeselector.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package routeselector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
|
route "github.com/netbirdio/netbird/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RouteSelector struct {
|
||||||
|
selectedRoutes map[route.NetID]struct{}
|
||||||
|
selectAll bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteSelector() *RouteSelector {
|
||||||
|
return &RouteSelector{
|
||||||
|
selectedRoutes: map[route.NetID]struct{}{},
|
||||||
|
// default selects all routes
|
||||||
|
selectAll: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectRoutes updates the selected routes based on the provided route IDs.
|
||||||
|
func (rs *RouteSelector) SelectRoutes(routes []route.NetID, appendRoute bool, allRoutes []route.NetID) error {
|
||||||
|
if !appendRoute {
|
||||||
|
rs.selectedRoutes = map[route.NetID]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiErr *multierror.Error
|
||||||
|
for _, route := range routes {
|
||||||
|
if !slices.Contains(allRoutes, route) {
|
||||||
|
multiErr = multierror.Append(multiErr, fmt.Errorf("route '%s' is not available", route))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.selectedRoutes[route] = struct{}{}
|
||||||
|
}
|
||||||
|
rs.selectAll = false
|
||||||
|
|
||||||
|
if multiErr != nil {
|
||||||
|
multiErr.ErrorFormat = formatError
|
||||||
|
}
|
||||||
|
|
||||||
|
return multiErr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectAllRoutes sets the selector to select all routes.
|
||||||
|
func (rs *RouteSelector) SelectAllRoutes() {
|
||||||
|
rs.selectAll = true
|
||||||
|
rs.selectedRoutes = map[route.NetID]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeselectRoutes removes specific routes from the selection.
|
||||||
|
// If the selector is in "select all" mode, it will transition to "select specific" mode.
|
||||||
|
func (rs *RouteSelector) DeselectRoutes(routes []route.NetID, allRoutes []route.NetID) error {
|
||||||
|
if rs.selectAll {
|
||||||
|
rs.selectAll = false
|
||||||
|
rs.selectedRoutes = map[route.NetID]struct{}{}
|
||||||
|
for _, route := range allRoutes {
|
||||||
|
rs.selectedRoutes[route] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiErr *multierror.Error
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
if !slices.Contains(allRoutes, route) {
|
||||||
|
multiErr = multierror.Append(multiErr, fmt.Errorf("route '%s' is not available", route))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(rs.selectedRoutes, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiErr != nil {
|
||||||
|
multiErr.ErrorFormat = formatError
|
||||||
|
}
|
||||||
|
|
||||||
|
return multiErr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeselectAllRoutes deselects all routes, effectively disabling route selection.
|
||||||
|
func (rs *RouteSelector) DeselectAllRoutes() {
|
||||||
|
rs.selectAll = false
|
||||||
|
rs.selectedRoutes = map[route.NetID]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSelected checks if a specific route is selected.
|
||||||
|
func (rs *RouteSelector) IsSelected(routeID route.NetID) bool {
|
||||||
|
if rs.selectAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, selected := rs.selectedRoutes[routeID]
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterSelected removes unselected routes from the provided map.
|
||||||
|
func (rs *RouteSelector) FilterSelected(routes route.HAMap) route.HAMap {
|
||||||
|
if rs.selectAll {
|
||||||
|
return maps.Clone(routes)
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := route.HAMap{}
|
||||||
|
for id, rt := range routes {
|
||||||
|
if rs.IsSelected(id.NetID()) {
|
||||||
|
filtered[id] = rt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatError(es []error) string {
|
||||||
|
if len(es) == 1 {
|
||||||
|
return fmt.Sprintf("1 error occurred:\n\t* %s", es[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
points := make([]string, len(es))
|
||||||
|
for i, err := range es {
|
||||||
|
points[i] = fmt.Sprintf("* %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%d errors occurred:\n\t%s",
|
||||||
|
len(es), strings.Join(points, "\n\t"))
|
||||||
|
}
|
||||||
275
client/internal/routeselector/routeselector_test.go
Normal file
275
client/internal/routeselector/routeselector_test.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
package routeselector_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routeselector"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRouteSelector_SelectRoutes(t *testing.T) {
|
||||||
|
allRoutes := []route.NetID{"route1", "route2", "route3"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialSelected []route.NetID
|
||||||
|
|
||||||
|
selectRoutes []route.NetID
|
||||||
|
append bool
|
||||||
|
|
||||||
|
wantSelected []route.NetID
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Select specific routes, initial all selected",
|
||||||
|
selectRoutes: []route.NetID{"route1", "route2"},
|
||||||
|
wantSelected: []route.NetID{"route1", "route2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Select specific routes, initial all deselected",
|
||||||
|
initialSelected: []route.NetID{},
|
||||||
|
selectRoutes: []route.NetID{"route1", "route2"},
|
||||||
|
wantSelected: []route.NetID{"route1", "route2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Select specific routes with initial selection",
|
||||||
|
initialSelected: []route.NetID{"route1"},
|
||||||
|
selectRoutes: []route.NetID{"route2", "route3"},
|
||||||
|
wantSelected: []route.NetID{"route2", "route3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Select non-existing route",
|
||||||
|
selectRoutes: []route.NetID{"route1", "route4"},
|
||||||
|
wantSelected: []route.NetID{"route1"},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Append route with initial selection",
|
||||||
|
initialSelected: []route.NetID{"route1"},
|
||||||
|
selectRoutes: []route.NetID{"route2"},
|
||||||
|
append: true,
|
||||||
|
wantSelected: []route.NetID{"route1", "route2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Append route without initial selection",
|
||||||
|
selectRoutes: []route.NetID{"route2"},
|
||||||
|
append: true,
|
||||||
|
wantSelected: []route.NetID{"route2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
if tt.initialSelected != nil {
|
||||||
|
err := rs.SelectRoutes(tt.initialSelected, false, allRoutes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rs.SelectRoutes(tt.selectRoutes, tt.append, allRoutes)
|
||||||
|
if tt.wantError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range allRoutes {
|
||||||
|
assert.Equal(t, rs.IsSelected(id), slices.Contains(tt.wantSelected, id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteSelector_SelectAllRoutes(t *testing.T) {
|
||||||
|
allRoutes := []route.NetID{"route1", "route2", "route3"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialSelected []route.NetID
|
||||||
|
|
||||||
|
wantSelected []route.NetID
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Initial all selected",
|
||||||
|
wantSelected: []route.NetID{"route1", "route2", "route3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Initial all deselected",
|
||||||
|
initialSelected: []route.NetID{},
|
||||||
|
wantSelected: []route.NetID{"route1", "route2", "route3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Initial some selected",
|
||||||
|
initialSelected: []route.NetID{"route1"},
|
||||||
|
wantSelected: []route.NetID{"route1", "route2", "route3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Initial all selected",
|
||||||
|
initialSelected: []route.NetID{"route1", "route2", "route3"},
|
||||||
|
wantSelected: []route.NetID{"route1", "route2", "route3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
if tt.initialSelected != nil {
|
||||||
|
err := rs.SelectRoutes(tt.initialSelected, false, allRoutes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.SelectAllRoutes()
|
||||||
|
|
||||||
|
for _, id := range allRoutes {
|
||||||
|
assert.Equal(t, rs.IsSelected(id), slices.Contains(tt.wantSelected, id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteSelector_DeselectRoutes(t *testing.T) {
|
||||||
|
allRoutes := []route.NetID{"route1", "route2", "route3"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialSelected []route.NetID
|
||||||
|
|
||||||
|
deselectRoutes []route.NetID
|
||||||
|
|
||||||
|
wantSelected []route.NetID
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Deselect specific routes, initial all selected",
|
||||||
|
deselectRoutes: []route.NetID{"route1", "route2"},
|
||||||
|
wantSelected: []route.NetID{"route3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Deselect specific routes, initial all deselected",
|
||||||
|
initialSelected: []route.NetID{},
|
||||||
|
deselectRoutes: []route.NetID{"route1", "route2"},
|
||||||
|
wantSelected: []route.NetID{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Deselect specific routes with initial selection",
|
||||||
|
initialSelected: []route.NetID{"route1", "route2"},
|
||||||
|
deselectRoutes: []route.NetID{"route1", "route3"},
|
||||||
|
wantSelected: []route.NetID{"route2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Deselect non-existing route",
|
||||||
|
initialSelected: []route.NetID{"route1", "route2"},
|
||||||
|
deselectRoutes: []route.NetID{"route1", "route4"},
|
||||||
|
wantSelected: []route.NetID{"route2"},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
if tt.initialSelected != nil {
|
||||||
|
err := rs.SelectRoutes(tt.initialSelected, false, allRoutes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rs.DeselectRoutes(tt.deselectRoutes, allRoutes)
|
||||||
|
if tt.wantError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range allRoutes {
|
||||||
|
assert.Equal(t, rs.IsSelected(id), slices.Contains(tt.wantSelected, id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteSelector_DeselectAll(t *testing.T) {
|
||||||
|
allRoutes := []route.NetID{"route1", "route2", "route3"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialSelected []route.NetID
|
||||||
|
|
||||||
|
wantSelected []route.NetID
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Initial all selected",
|
||||||
|
wantSelected: []route.NetID{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Initial all deselected",
|
||||||
|
initialSelected: []route.NetID{},
|
||||||
|
wantSelected: []route.NetID{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Initial some selected",
|
||||||
|
initialSelected: []route.NetID{"route1", "route2"},
|
||||||
|
wantSelected: []route.NetID{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Initial all selected",
|
||||||
|
initialSelected: []route.NetID{"route1", "route2", "route3"},
|
||||||
|
wantSelected: []route.NetID{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
if tt.initialSelected != nil {
|
||||||
|
err := rs.SelectRoutes(tt.initialSelected, false, allRoutes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.DeselectAllRoutes()
|
||||||
|
|
||||||
|
for _, id := range allRoutes {
|
||||||
|
assert.Equal(t, rs.IsSelected(id), slices.Contains(tt.wantSelected, id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteSelector_IsSelected(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
err := rs.SelectRoutes([]route.NetID{"route1", "route2"}, false, []route.NetID{"route1", "route2", "route3"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.True(t, rs.IsSelected("route1"))
|
||||||
|
assert.True(t, rs.IsSelected("route2"))
|
||||||
|
assert.False(t, rs.IsSelected("route3"))
|
||||||
|
assert.False(t, rs.IsSelected("route4"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteSelector_FilterSelected(t *testing.T) {
|
||||||
|
rs := routeselector.NewRouteSelector()
|
||||||
|
|
||||||
|
err := rs.SelectRoutes([]route.NetID{"route1", "route2"}, false, []route.NetID{"route1", "route2", "route3"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
routes := route.HAMap{
|
||||||
|
"route1-10.0.0.0/8": {},
|
||||||
|
"route2-192.168.0.0/16": {},
|
||||||
|
"route3-172.16.0.0/12": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := rs.FilterSelected(routes)
|
||||||
|
|
||||||
|
assert.Equal(t, route.HAMap{
|
||||||
|
"route1-10.0.0.0/8": {},
|
||||||
|
"route2-192.168.0.0/16": {},
|
||||||
|
}, filtered)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package stdnet
|
package stdnet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -19,7 +20,7 @@ func InterfaceFilter(disallowList []string) func(string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range disallowList {
|
for _, s := range disallowList {
|
||||||
if strings.HasPrefix(iFace, s) {
|
if strings.HasPrefix(iFace, s) && runtime.GOOS != "ios" {
|
||||||
log.Tracef("ignoring interface %s - it is not allowed", iFace)
|
log.Tracef("ignoring interface %s - it is not allowed", iFace)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package wgproxy
|
package wgproxy
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
wgPort int
|
wgPort int
|
||||||
ebpfProxy Proxy
|
ebpfProxy Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Factory) GetProxy() Proxy {
|
func (w *Factory) GetProxy(ctx context.Context) Proxy {
|
||||||
if w.ebpfProxy != nil {
|
if w.ebpfProxy != nil {
|
||||||
return w.ebpfProxy
|
return w.ebpfProxy
|
||||||
}
|
}
|
||||||
return NewWGUserSpaceProxy(w.wgPort)
|
return NewWGUserSpaceProxy(ctx, w.wgPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Factory) Free() error {
|
func (w *Factory) Free() error {
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
package wgproxy
|
package wgproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewFactory(wgPort int) *Factory {
|
func NewFactory(ctx context.Context, wgPort int) *Factory {
|
||||||
f := &Factory{wgPort: wgPort}
|
f := &Factory{wgPort: wgPort}
|
||||||
|
|
||||||
ebpfProxy := NewWGEBPFProxy(wgPort)
|
ebpfProxy := NewWGEBPFProxy(ctx, wgPort)
|
||||||
err := ebpfProxy.Listen()
|
err := ebpfProxy.listen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to initialize ebpf proxy, fallback to user space proxy: %s", err)
|
log.Warnf("failed to initialize ebpf proxy, fallback to user space proxy: %s", err)
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
package wgproxy
|
package wgproxy
|
||||||
|
|
||||||
func NewFactory(wgPort int) *Factory {
|
import "context"
|
||||||
|
|
||||||
|
func NewFactory(ctx context.Context, wgPort int) *Factory {
|
||||||
return &Factory{wgPort: wgPort}
|
return &Factory{wgPort: wgPort}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
// Proxy is a transfer layer between the Turn connection and the WireGuard
|
// Proxy is a transfer layer between the Turn connection and the WireGuard
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
AddTurnConn(urnConn net.Conn) (net.Addr, error)
|
AddTurnConn(turnConn net.Conn) (net.Addr, error)
|
||||||
CloseConn() error
|
CloseConn() error
|
||||||
Free() error
|
Free() error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package wgproxy
|
package wgproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -22,7 +23,11 @@ import (
|
|||||||
|
|
||||||
// WGEBPFProxy definition for proxy with EBPF support
|
// WGEBPFProxy definition for proxy with EBPF support
|
||||||
type WGEBPFProxy struct {
|
type WGEBPFProxy struct {
|
||||||
ebpfManager ebpfMgr.Manager
|
ebpfManager ebpfMgr.Manager
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
lastUsedPort uint16
|
lastUsedPort uint16
|
||||||
localWGListenPort int
|
localWGListenPort int
|
||||||
|
|
||||||
@@ -34,7 +39,7 @@ type WGEBPFProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWGEBPFProxy create new WGEBPFProxy instance
|
// NewWGEBPFProxy create new WGEBPFProxy instance
|
||||||
func NewWGEBPFProxy(wgPort int) *WGEBPFProxy {
|
func NewWGEBPFProxy(ctx context.Context, wgPort int) *WGEBPFProxy {
|
||||||
log.Debugf("instantiate ebpf proxy")
|
log.Debugf("instantiate ebpf proxy")
|
||||||
wgProxy := &WGEBPFProxy{
|
wgProxy := &WGEBPFProxy{
|
||||||
localWGListenPort: wgPort,
|
localWGListenPort: wgPort,
|
||||||
@@ -42,11 +47,13 @@ func NewWGEBPFProxy(wgPort int) *WGEBPFProxy {
|
|||||||
lastUsedPort: 0,
|
lastUsedPort: 0,
|
||||||
turnConnStore: make(map[uint16]net.Conn),
|
turnConnStore: make(map[uint16]net.Conn),
|
||||||
}
|
}
|
||||||
|
wgProxy.ctx, wgProxy.cancel = context.WithCancel(ctx)
|
||||||
|
|
||||||
return wgProxy
|
return wgProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen load ebpf program and listen the proxy
|
// listen load ebpf program and listen the proxy
|
||||||
func (p *WGEBPFProxy) Listen() error {
|
func (p *WGEBPFProxy) listen() error {
|
||||||
pl := portLookup{}
|
pl := portLookup{}
|
||||||
wgPorxyPort, err := pl.searchFreePort()
|
wgPorxyPort, err := pl.searchFreePort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,7 +79,7 @@ func (p *WGEBPFProxy) Listen() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
cErr := p.Free()
|
cErr := p.Free()
|
||||||
if cErr != nil {
|
if cErr != nil {
|
||||||
log.Errorf("failed to close the wgproxy: %s", cErr)
|
log.Errorf("Failed to close the wgproxy: %s", cErr)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -131,19 +138,27 @@ func (p *WGEBPFProxy) Free() error {
|
|||||||
|
|
||||||
func (p *WGEBPFProxy) proxyToLocal(endpointPort uint16, remoteConn net.Conn) {
|
func (p *WGEBPFProxy) proxyToLocal(endpointPort uint16, remoteConn net.Conn) {
|
||||||
buf := make([]byte, 1500)
|
buf := make([]byte, 1500)
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
p.removeTurnConn(endpointPort)
|
||||||
|
}()
|
||||||
for {
|
for {
|
||||||
n, err := remoteConn.Read(buf)
|
select {
|
||||||
if err != nil {
|
case <-p.ctx.Done():
|
||||||
if err != io.EOF {
|
|
||||||
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err)
|
|
||||||
}
|
|
||||||
p.removeTurnConn(endpointPort)
|
|
||||||
log.Infof("stop forward turn packages to port: %d. error: %s", endpointPort, err)
|
|
||||||
return
|
return
|
||||||
}
|
default:
|
||||||
err = p.sendPkg(buf[:n], endpointPort)
|
var n int
|
||||||
if err != nil {
|
n, err = remoteConn.Read(buf)
|
||||||
log.Errorf("failed to write out turn pkg to local conn: %v", err)
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.sendPkg(buf[:n], endpointPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to write out turn pkg to local conn: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,23 +167,28 @@ func (p *WGEBPFProxy) proxyToLocal(endpointPort uint16, remoteConn net.Conn) {
|
|||||||
func (p *WGEBPFProxy) proxyToRemote() {
|
func (p *WGEBPFProxy) proxyToRemote() {
|
||||||
buf := make([]byte, 1500)
|
buf := make([]byte, 1500)
|
||||||
for {
|
for {
|
||||||
n, addr, err := p.conn.ReadFromUDP(buf)
|
select {
|
||||||
if err != nil {
|
case <-p.ctx.Done():
|
||||||
log.Errorf("failed to read UDP pkg from WG: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
default:
|
||||||
|
n, addr, err := p.conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to read UDP pkg from WG: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
p.turnConnMutex.Lock()
|
p.turnConnMutex.Lock()
|
||||||
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
||||||
p.turnConnMutex.Unlock()
|
p.turnConnMutex.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Infof("turn conn not found by port: %d", addr.Port)
|
log.Infof("turn conn not found by port: %d", addr.Port)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.Write(buf[:n])
|
_, err = conn.Write(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to forward local wg pkg (%d) to remote turn conn: %s", addr.Port, err)
|
log.Debugf("failed to forward local wg pkg (%d) to remote turn conn: %s", addr.Port, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,15 +286,17 @@ func (p *WGEBPFProxy) sendPkg(data []byte, port uint16) error {
|
|||||||
|
|
||||||
err := udpH.SetNetworkLayerForChecksum(ipH)
|
err := udpH.SetNetworkLayerForChecksum(ipH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("set network layer for checksum: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
layerBuffer := gopacket.NewSerializeBuffer()
|
layerBuffer := gopacket.NewSerializeBuffer()
|
||||||
|
|
||||||
err = gopacket.SerializeLayers(layerBuffer, gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}, ipH, udpH, payload)
|
err = gopacket.SerializeLayers(layerBuffer, gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}, ipH, udpH, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("serialize layers: %w", err)
|
||||||
}
|
}
|
||||||
_, err = p.rawConn.WriteTo(layerBuffer.Bytes(), &net.IPAddr{IP: localhost})
|
if _, err = p.rawConn.WriteTo(layerBuffer.Bytes(), &net.IPAddr{IP: localhost}); err != nil {
|
||||||
return err
|
return fmt.Errorf("write to raw conn: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
package wgproxy
|
package wgproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWGEBPFProxy_connStore(t *testing.T) {
|
func TestWGEBPFProxy_connStore(t *testing.T) {
|
||||||
wgProxy := NewWGEBPFProxy(1)
|
wgProxy := NewWGEBPFProxy(context.Background(), 1)
|
||||||
|
|
||||||
p, _ := wgProxy.storeTurnConn(nil)
|
p, _ := wgProxy.storeTurnConn(nil)
|
||||||
if p != 1 {
|
if p != 1 {
|
||||||
@@ -27,7 +28,7 @@ func TestWGEBPFProxy_connStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWGEBPFProxy_portCalculation_overflow(t *testing.T) {
|
func TestWGEBPFProxy_portCalculation_overflow(t *testing.T) {
|
||||||
wgProxy := NewWGEBPFProxy(1)
|
wgProxy := NewWGEBPFProxy(context.Background(), 1)
|
||||||
|
|
||||||
_, _ = wgProxy.storeTurnConn(nil)
|
_, _ = wgProxy.storeTurnConn(nil)
|
||||||
wgProxy.lastUsedPort = 65535
|
wgProxy.lastUsedPort = 65535
|
||||||
@@ -43,7 +44,7 @@ func TestWGEBPFProxy_portCalculation_overflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWGEBPFProxy_portCalculation_maxConn(t *testing.T) {
|
func TestWGEBPFProxy_portCalculation_maxConn(t *testing.T) {
|
||||||
wgProxy := NewWGEBPFProxy(1)
|
wgProxy := NewWGEBPFProxy(context.Background(), 1)
|
||||||
|
|
||||||
for i := 0; i < 65535; i++ {
|
for i := 0; i < 65535; i++ {
|
||||||
_, _ = wgProxy.storeTurnConn(nil)
|
_, _ = wgProxy.storeTurnConn(nil)
|
||||||
|
|||||||
@@ -21,21 +21,21 @@ type WGUserSpaceProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWGUserSpaceProxy instantiate a user space WireGuard proxy
|
// NewWGUserSpaceProxy instantiate a user space WireGuard proxy
|
||||||
func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy {
|
func NewWGUserSpaceProxy(ctx context.Context, wgPort int) *WGUserSpaceProxy {
|
||||||
log.Debugf("instantiate new userspace proxy")
|
log.Debugf("Initializing new user space proxy with port %d", wgPort)
|
||||||
p := &WGUserSpaceProxy{
|
p := &WGUserSpaceProxy{
|
||||||
localWGListenPort: wgPort,
|
localWGListenPort: wgPort,
|
||||||
}
|
}
|
||||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
p.ctx, p.cancel = context.WithCancel(ctx)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTurnConn start the proxy with the given remote conn
|
// AddTurnConn start the proxy with the given remote conn
|
||||||
func (p *WGUserSpaceProxy) AddTurnConn(remoteConn net.Conn) (net.Addr, error) {
|
func (p *WGUserSpaceProxy) AddTurnConn(turnConn net.Conn) (net.Addr, error) {
|
||||||
p.remoteConn = remoteConn
|
p.remoteConn = turnConn
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
p.localConn, err = nbnet.NewDialer().Dial("udp", fmt.Sprintf(":%d", p.localWGListenPort))
|
p.localConn, err = nbnet.NewDialer().DialContext(p.ctx, "udp", fmt.Sprintf(":%d", p.localWGListenPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed dialing to local Wireguard port %s", err)
|
log.Errorf("failed dialing to local Wireguard port %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ package NetBirdSDK
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
@@ -14,6 +19,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/formatter"
|
"github.com/netbirdio/netbird/formatter"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnectionListener export internal Listener for mobile
|
// ConnectionListener export internal Listener for mobile
|
||||||
@@ -38,6 +44,12 @@ type CustomLogger interface {
|
|||||||
Error(message string)
|
Error(message string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type selectRoute struct {
|
||||||
|
NetID string
|
||||||
|
Network netip.Prefix
|
||||||
|
Selected bool
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
formatter.SetLogcatFormatter(log.StandardLogger())
|
formatter.SetLogcatFormatter(log.StandardLogger())
|
||||||
}
|
}
|
||||||
@@ -55,6 +67,7 @@ type Client struct {
|
|||||||
onHostDnsFn func([]string)
|
onHostDnsFn func([]string)
|
||||||
dnsManager dns.IosDnsManager
|
dnsManager dns.IosDnsManager
|
||||||
loginComplete bool
|
loginComplete bool
|
||||||
|
connectClient *internal.ConnectClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
@@ -107,7 +120,9 @@ func (c *Client) Run(fd int32, interfaceName string) error {
|
|||||||
ctx = internal.CtxInitState(ctx)
|
ctx = internal.CtxInitState(ctx)
|
||||||
c.onHostDnsFn = func([]string) {}
|
c.onHostDnsFn = func([]string) {}
|
||||||
cfg.WgIface = interfaceName
|
cfg.WgIface = interfaceName
|
||||||
return internal.RunClientiOS(ctx, cfg, c.recorder, fd, c.networkChangeListener, c.dnsManager)
|
|
||||||
|
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
|
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the internal client and free the resources
|
// Stop the internal client and free the resources
|
||||||
@@ -133,10 +148,29 @@ func (c *Client) GetStatusDetails() *StatusDetails {
|
|||||||
|
|
||||||
peerInfos := make([]PeerInfo, len(fullStatus.Peers))
|
peerInfos := make([]PeerInfo, len(fullStatus.Peers))
|
||||||
for n, p := range fullStatus.Peers {
|
for n, p := range fullStatus.Peers {
|
||||||
|
var routes = RoutesDetails{}
|
||||||
|
for r := range p.GetRoutes() {
|
||||||
|
routeInfo := RoutesInfo{r}
|
||||||
|
routes.items = append(routes.items, routeInfo)
|
||||||
|
}
|
||||||
pi := PeerInfo{
|
pi := PeerInfo{
|
||||||
p.IP,
|
IP: p.IP,
|
||||||
p.FQDN,
|
FQDN: p.FQDN,
|
||||||
p.ConnStatus.String(),
|
LocalIceCandidateEndpoint: p.LocalIceCandidateEndpoint,
|
||||||
|
RemoteIceCandidateEndpoint: p.RemoteIceCandidateEndpoint,
|
||||||
|
LocalIceCandidateType: p.LocalIceCandidateType,
|
||||||
|
RemoteIceCandidateType: p.RemoteIceCandidateType,
|
||||||
|
PubKey: p.PubKey,
|
||||||
|
Latency: formatDuration(p.Latency),
|
||||||
|
BytesRx: p.BytesRx,
|
||||||
|
BytesTx: p.BytesTx,
|
||||||
|
ConnStatus: p.ConnStatus.String(),
|
||||||
|
ConnStatusUpdate: p.ConnStatusUpdate.Format("2006-01-02 15:04:05"),
|
||||||
|
Direct: p.Direct,
|
||||||
|
LastWireguardHandshake: p.LastWireguardHandshake.String(),
|
||||||
|
Relayed: p.Relayed,
|
||||||
|
RosenpassEnabled: p.RosenpassEnabled,
|
||||||
|
Routes: routes,
|
||||||
}
|
}
|
||||||
peerInfos[n] = pi
|
peerInfos[n] = pi
|
||||||
}
|
}
|
||||||
@@ -223,3 +257,142 @@ func (c *Client) IsLoginComplete() bool {
|
|||||||
func (c *Client) ClearLoginComplete() {
|
func (c *Client) ClearLoginComplete() {
|
||||||
c.loginComplete = false
|
c.loginComplete = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
|
||||||
|
if c.connectClient == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := c.connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
routesMap := engine.GetClientRoutesWithNetID()
|
||||||
|
routeSelector := engine.GetRouteManager().GetRouteSelector()
|
||||||
|
|
||||||
|
var routes []*selectRoute
|
||||||
|
for id, rt := range routesMap {
|
||||||
|
if len(rt) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
route := &selectRoute{
|
||||||
|
NetID: string(id),
|
||||||
|
Network: rt[0].Network,
|
||||||
|
Selected: routeSelector.IsSelected(id),
|
||||||
|
}
|
||||||
|
routes = append(routes, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(routes, func(i, j int) bool {
|
||||||
|
iPrefix := routes[i].Network.Bits()
|
||||||
|
jPrefix := routes[j].Network.Bits()
|
||||||
|
|
||||||
|
if iPrefix == jPrefix {
|
||||||
|
iAddr := routes[i].Network.Addr()
|
||||||
|
jAddr := routes[j].Network.Addr()
|
||||||
|
if iAddr == jAddr {
|
||||||
|
return routes[i].NetID < routes[j].NetID
|
||||||
|
}
|
||||||
|
return iAddr.String() < jAddr.String()
|
||||||
|
}
|
||||||
|
return iPrefix < jPrefix
|
||||||
|
})
|
||||||
|
|
||||||
|
var routeSelection []RoutesSelectionInfo
|
||||||
|
for _, r := range routes {
|
||||||
|
routeSelection = append(routeSelection, RoutesSelectionInfo{
|
||||||
|
ID: r.NetID,
|
||||||
|
Network: r.Network.String(),
|
||||||
|
Selected: r.Selected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
routeSelectionDetails := RoutesSelectionDetails{items: routeSelection}
|
||||||
|
return &routeSelectionDetails, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SelectRoute(id string) error {
|
||||||
|
if c.connectClient == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := c.connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
routeManager := engine.GetRouteManager()
|
||||||
|
routeSelector := routeManager.GetRouteSelector()
|
||||||
|
if id == "All" {
|
||||||
|
log.Debugf("select all routes")
|
||||||
|
routeSelector.SelectAllRoutes()
|
||||||
|
} else {
|
||||||
|
log.Debugf("select route with id: %s", id)
|
||||||
|
routes := toNetIDs([]string{id})
|
||||||
|
if err := routeSelector.SelectRoutes(routes, true, maps.Keys(engine.GetClientRoutesWithNetID())); err != nil {
|
||||||
|
log.Debugf("error when selecting routes: %s", err)
|
||||||
|
return fmt.Errorf("select routes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routeManager.TriggerSelection(engine.GetClientRoutes())
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeselectRoute(id string) error {
|
||||||
|
if c.connectClient == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
engine := c.connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
routeManager := engine.GetRouteManager()
|
||||||
|
routeSelector := routeManager.GetRouteSelector()
|
||||||
|
if id == "All" {
|
||||||
|
log.Debugf("deselect all routes")
|
||||||
|
routeSelector.DeselectAllRoutes()
|
||||||
|
} else {
|
||||||
|
log.Debugf("deselect route with id: %s", id)
|
||||||
|
routes := toNetIDs([]string{id})
|
||||||
|
if err := routeSelector.DeselectRoutes(routes, maps.Keys(engine.GetClientRoutesWithNetID())); err != nil {
|
||||||
|
log.Debugf("error when deselecting routes: %s", err)
|
||||||
|
return fmt.Errorf("deselect routes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routeManager.TriggerSelection(engine.GetClientRoutes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
ds := d.String()
|
||||||
|
dotIndex := strings.Index(ds, ".")
|
||||||
|
if dotIndex != -1 {
|
||||||
|
// Determine end of numeric part, ensuring we stop at two decimal places or the actual end if fewer
|
||||||
|
endIndex := dotIndex + 3
|
||||||
|
if endIndex > len(ds) {
|
||||||
|
endIndex = len(ds)
|
||||||
|
}
|
||||||
|
// Find where the numeric part ends by finding the first non-digit character after the dot
|
||||||
|
unitStart := endIndex
|
||||||
|
for unitStart < len(ds) && (ds[unitStart] >= '0' && ds[unitStart] <= '9') {
|
||||||
|
unitStart++
|
||||||
|
}
|
||||||
|
// Ensures that we only take the unit characters after the numerical part
|
||||||
|
if unitStart < len(ds) {
|
||||||
|
return ds[:endIndex] + ds[unitStart:]
|
||||||
|
}
|
||||||
|
return ds[:endIndex] // In case no units are found after the digits
|
||||||
|
}
|
||||||
|
return ds
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNetIDs(routes []string) []route.NetID {
|
||||||
|
var netIDs []route.NetID
|
||||||
|
for _, rt := range routes {
|
||||||
|
netIDs = append(netIDs, route.NetID(rt))
|
||||||
|
}
|
||||||
|
return netIDs
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,28 @@ package NetBirdSDK
|
|||||||
|
|
||||||
// PeerInfo describe information about the peers. It designed for the UI usage
|
// PeerInfo describe information about the peers. It designed for the UI usage
|
||||||
type PeerInfo struct {
|
type PeerInfo struct {
|
||||||
IP string
|
IP string
|
||||||
FQDN string
|
FQDN string
|
||||||
ConnStatus string // Todo replace to enum
|
LocalIceCandidateEndpoint string
|
||||||
|
RemoteIceCandidateEndpoint string
|
||||||
|
LocalIceCandidateType string
|
||||||
|
RemoteIceCandidateType string
|
||||||
|
PubKey string
|
||||||
|
Latency string
|
||||||
|
BytesRx int64
|
||||||
|
BytesTx int64
|
||||||
|
ConnStatus string
|
||||||
|
ConnStatusUpdate string
|
||||||
|
Direct bool
|
||||||
|
LastWireguardHandshake string
|
||||||
|
Relayed bool
|
||||||
|
RosenpassEnabled bool
|
||||||
|
Routes RoutesDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoutes return with RouteDetails
|
||||||
|
func (p PeerInfo) GetRouteDetails() *RoutesDetails {
|
||||||
|
return &p.Routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerInfoCollection made for Java layer to get non default types as collection
|
// PeerInfoCollection made for Java layer to get non default types as collection
|
||||||
@@ -16,6 +35,21 @@ type PeerInfoCollection interface {
|
|||||||
GetIP() string
|
GetIP() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoutesInfoCollection made for Java layer to get non default types as collection
|
||||||
|
type RoutesInfoCollection interface {
|
||||||
|
Add(s string) RoutesInfoCollection
|
||||||
|
Get(i int) string
|
||||||
|
Size() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoutesDetails struct {
|
||||||
|
items []RoutesInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoutesInfo struct {
|
||||||
|
Route string
|
||||||
|
}
|
||||||
|
|
||||||
// StatusDetails is the implementation of the PeerInfoCollection
|
// StatusDetails is the implementation of the PeerInfoCollection
|
||||||
type StatusDetails struct {
|
type StatusDetails struct {
|
||||||
items []PeerInfo
|
items []PeerInfo
|
||||||
@@ -23,6 +57,22 @@ type StatusDetails struct {
|
|||||||
ip string
|
ip string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add new PeerInfo to the collection
|
||||||
|
func (array RoutesDetails) Add(s RoutesInfo) RoutesDetails {
|
||||||
|
array.items = append(array.items, s)
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return an element of the collection
|
||||||
|
func (array RoutesDetails) Get(i int) *RoutesInfo {
|
||||||
|
return &array.items[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size return with the size of the collection
|
||||||
|
func (array RoutesDetails) Size() int {
|
||||||
|
return len(array.items)
|
||||||
|
}
|
||||||
|
|
||||||
// Add new PeerInfo to the collection
|
// Add new PeerInfo to the collection
|
||||||
func (array StatusDetails) Add(s PeerInfo) StatusDetails {
|
func (array StatusDetails) Add(s PeerInfo) StatusDetails {
|
||||||
array.items = append(array.items, s)
|
array.items = append(array.items, s)
|
||||||
|
|||||||
@@ -71,6 +71,42 @@ func (p *Preferences) SetPreSharedKey(key string) {
|
|||||||
p.configInput.PreSharedKey = &key
|
p.configInput.PreSharedKey = &key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRosenpassEnabled store if rosenpass is enabled
|
||||||
|
func (p *Preferences) SetRosenpassEnabled(enabled bool) {
|
||||||
|
p.configInput.RosenpassEnabled = &enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRosenpassEnabled read rosenpass enabled from config file
|
||||||
|
func (p *Preferences) GetRosenpassEnabled() (bool, error) {
|
||||||
|
if p.configInput.RosenpassEnabled != nil {
|
||||||
|
return *p.configInput.RosenpassEnabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.RosenpassEnabled, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRosenpassPermissive store the given permissive and wait for commit
|
||||||
|
func (p *Preferences) SetRosenpassPermissive(permissive bool) {
|
||||||
|
p.configInput.RosenpassPermissive = &permissive
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRosenpassPermissive read rosenpass permissive from config file
|
||||||
|
func (p *Preferences) GetRosenpassPermissive() (bool, error) {
|
||||||
|
if p.configInput.RosenpassPermissive != nil {
|
||||||
|
return *p.configInput.RosenpassPermissive, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.RosenpassPermissive, err
|
||||||
|
}
|
||||||
|
|
||||||
// Commit write out the changes into config file
|
// Commit write out the changes into config file
|
||||||
func (p *Preferences) Commit() error {
|
func (p *Preferences) Commit() error {
|
||||||
_, err := internal.UpdateOrCreateConfig(p.configInput)
|
_, err := internal.UpdateOrCreateConfig(p.configInput)
|
||||||
|
|||||||
36
client/ios/NetBirdSDK/routes.go
Normal file
36
client/ios/NetBirdSDK/routes.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
// RoutesSelectionInfoCollection made for Java layer to get non default types as collection
|
||||||
|
type RoutesSelectionInfoCollection interface {
|
||||||
|
Add(s string) RoutesSelectionInfoCollection
|
||||||
|
Get(i int) string
|
||||||
|
Size() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoutesSelectionDetails struct {
|
||||||
|
All bool
|
||||||
|
Append bool
|
||||||
|
items []RoutesSelectionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoutesSelectionInfo struct {
|
||||||
|
ID string
|
||||||
|
Network string
|
||||||
|
Selected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new PeerInfo to the collection
|
||||||
|
func (array RoutesSelectionDetails) Add(s RoutesSelectionInfo) RoutesSelectionDetails {
|
||||||
|
array.items = append(array.items, s)
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return an element of the collection
|
||||||
|
func (array RoutesSelectionDetails) Get(i int) *RoutesSelectionInfo {
|
||||||
|
return &array.items[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size return with the size of the collection
|
||||||
|
func (array RoutesSelectionDetails) Size() int {
|
||||||
|
return len(array.items)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,21 @@ service DaemonService {
|
|||||||
|
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
rpc GetConfig(GetConfigRequest) returns (GetConfigResponse) {}
|
rpc GetConfig(GetConfigRequest) returns (GetConfigResponse) {}
|
||||||
|
|
||||||
|
// List available network routes
|
||||||
|
rpc ListRoutes(ListRoutesRequest) returns (ListRoutesResponse) {}
|
||||||
|
|
||||||
|
// Select specific routes
|
||||||
|
rpc SelectRoutes(SelectRoutesRequest) returns (SelectRoutesResponse) {}
|
||||||
|
|
||||||
|
// Deselect specific routes
|
||||||
|
rpc DeselectRoutes(SelectRoutesRequest) returns (SelectRoutesResponse) {}
|
||||||
|
|
||||||
|
// DebugBundle creates a debug bundle
|
||||||
|
rpc DebugBundle(DebugBundleRequest) returns (DebugBundleResponse) {}
|
||||||
|
|
||||||
|
// SetLogLevel sets the log level of the daemon
|
||||||
|
rpc SetLogLevel(SetLogLevelRequest) returns (SetLogLevelResponse) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
message LoginRequest {
|
message LoginRequest {
|
||||||
@@ -72,6 +87,8 @@ message LoginRequest {
|
|||||||
optional bool rosenpassPermissive = 16;
|
optional bool rosenpassPermissive = 16;
|
||||||
|
|
||||||
repeated string extraIFaceBlacklist = 17;
|
repeated string extraIFaceBlacklist = 17;
|
||||||
|
|
||||||
|
optional bool networkMonitor = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
@@ -195,4 +212,53 @@ message FullStatus {
|
|||||||
repeated PeerState peers = 4;
|
repeated PeerState peers = 4;
|
||||||
repeated RelayState relays = 5;
|
repeated RelayState relays = 5;
|
||||||
repeated NSGroupState dns_servers = 6;
|
repeated NSGroupState dns_servers = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRoutesRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRoutesResponse {
|
||||||
|
repeated Route routes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SelectRoutesRequest {
|
||||||
|
repeated string routeIDs = 1;
|
||||||
|
bool append = 2;
|
||||||
|
bool all = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SelectRoutesResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
message Route {
|
||||||
|
string ID = 1;
|
||||||
|
string network = 2;
|
||||||
|
bool selected = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DebugBundleRequest {
|
||||||
|
bool anonymize = 1;
|
||||||
|
string status = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DebugBundleResponse {
|
||||||
|
string path = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogLevel {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
PANIC = 1;
|
||||||
|
FATAL = 2;
|
||||||
|
ERROR = 3;
|
||||||
|
WARN = 4;
|
||||||
|
INFO = 5;
|
||||||
|
DEBUG = 6;
|
||||||
|
TRACE = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetLogLevelRequest {
|
||||||
|
LogLevel level = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetLogLevelResponse {
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,16 @@ type DaemonServiceClient interface {
|
|||||||
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
|
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error)
|
GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error)
|
||||||
|
// List available network routes
|
||||||
|
ListRoutes(ctx context.Context, in *ListRoutesRequest, opts ...grpc.CallOption) (*ListRoutesResponse, error)
|
||||||
|
// Select specific routes
|
||||||
|
SelectRoutes(ctx context.Context, in *SelectRoutesRequest, opts ...grpc.CallOption) (*SelectRoutesResponse, error)
|
||||||
|
// Deselect specific routes
|
||||||
|
DeselectRoutes(ctx context.Context, in *SelectRoutesRequest, opts ...grpc.CallOption) (*SelectRoutesResponse, error)
|
||||||
|
// DebugBundle creates a debug bundle
|
||||||
|
DebugBundle(ctx context.Context, in *DebugBundleRequest, opts ...grpc.CallOption) (*DebugBundleResponse, error)
|
||||||
|
// SetLogLevel sets the log level of the daemon
|
||||||
|
SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type daemonServiceClient struct {
|
type daemonServiceClient struct {
|
||||||
@@ -95,6 +105,51 @@ func (c *daemonServiceClient) GetConfig(ctx context.Context, in *GetConfigReques
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) ListRoutes(ctx context.Context, in *ListRoutesRequest, opts ...grpc.CallOption) (*ListRoutesResponse, error) {
|
||||||
|
out := new(ListRoutesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListRoutes", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) SelectRoutes(ctx context.Context, in *SelectRoutesRequest, opts ...grpc.CallOption) (*SelectRoutesResponse, error) {
|
||||||
|
out := new(SelectRoutesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SelectRoutes", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) DeselectRoutes(ctx context.Context, in *SelectRoutesRequest, opts ...grpc.CallOption) (*SelectRoutesResponse, error) {
|
||||||
|
out := new(SelectRoutesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/DeselectRoutes", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) DebugBundle(ctx context.Context, in *DebugBundleRequest, opts ...grpc.CallOption) (*DebugBundleResponse, error) {
|
||||||
|
out := new(DebugBundleResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/DebugBundle", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) {
|
||||||
|
out := new(SetLogLevelResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetLogLevel", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonServiceServer is the server API for DaemonService service.
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
// All implementations must embed UnimplementedDaemonServiceServer
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -112,6 +167,16 @@ type DaemonServiceServer interface {
|
|||||||
Down(context.Context, *DownRequest) (*DownResponse, error)
|
Down(context.Context, *DownRequest) (*DownResponse, error)
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error)
|
GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error)
|
||||||
|
// List available network routes
|
||||||
|
ListRoutes(context.Context, *ListRoutesRequest) (*ListRoutesResponse, error)
|
||||||
|
// Select specific routes
|
||||||
|
SelectRoutes(context.Context, *SelectRoutesRequest) (*SelectRoutesResponse, error)
|
||||||
|
// Deselect specific routes
|
||||||
|
DeselectRoutes(context.Context, *SelectRoutesRequest) (*SelectRoutesResponse, error)
|
||||||
|
// DebugBundle creates a debug bundle
|
||||||
|
DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error)
|
||||||
|
// SetLogLevel sets the log level of the daemon
|
||||||
|
SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error)
|
||||||
mustEmbedUnimplementedDaemonServiceServer()
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +202,21 @@ func (UnimplementedDaemonServiceServer) Down(context.Context, *DownRequest) (*Do
|
|||||||
func (UnimplementedDaemonServiceServer) GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) {
|
func (UnimplementedDaemonServiceServer) GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) ListRoutes(context.Context, *ListRoutesRequest) (*ListRoutesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListRoutes not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) SelectRoutes(context.Context, *SelectRoutesRequest) (*SelectRoutesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SelectRoutes not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) DeselectRoutes(context.Context, *SelectRoutesRequest) (*SelectRoutesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DeselectRoutes not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DebugBundle not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SetLogLevel not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
|
|
||||||
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -258,6 +338,96 @@ func _DaemonService_GetConfig_Handler(srv interface{}, ctx context.Context, dec
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _DaemonService_ListRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListRoutesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).ListRoutes(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/ListRoutes",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).ListRoutes(ctx, req.(*ListRoutesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_SelectRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SelectRoutesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).SelectRoutes(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/SelectRoutes",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).SelectRoutes(ctx, req.(*SelectRoutesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_DeselectRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SelectRoutesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).DeselectRoutes(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/DeselectRoutes",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).DeselectRoutes(ctx, req.(*SelectRoutesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_DebugBundle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DebugBundleRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).DebugBundle(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/DebugBundle",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).DebugBundle(ctx, req.(*DebugBundleRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetLogLevelRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).SetLogLevel(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/SetLogLevel",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).SetLogLevel(ctx, req.(*SetLogLevelRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -289,6 +459,26 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetConfig",
|
MethodName: "GetConfig",
|
||||||
Handler: _DaemonService_GetConfig_Handler,
|
Handler: _DaemonService_GetConfig_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListRoutes",
|
||||||
|
Handler: _DaemonService_ListRoutes_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SelectRoutes",
|
||||||
|
Handler: _DaemonService_SelectRoutes_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeselectRoutes",
|
||||||
|
Handler: _DaemonService_DeselectRoutes_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DebugBundle",
|
||||||
|
Handler: _DaemonService_DebugBundle_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetLogLevel",
|
||||||
|
Handler: _DaemonService_SetLogLevel_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "daemon.proto",
|
Metadata: "daemon.proto",
|
||||||
|
|||||||
175
client/server/debug.go
Normal file
175
client/server/debug.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/anonymize"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugBundle creates a debug bundle and returns the location.
|
||||||
|
func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (resp *proto.DebugBundleResponse, err error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.logFile == "console" {
|
||||||
|
return nil, fmt.Errorf("log file is set to console, cannot create debug bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
bundlePath, err := os.CreateTemp("", "netbird.debug.*.zip")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create zip file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := bundlePath.Close(); err != nil {
|
||||||
|
log.Errorf("failed to close zip file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err2 := os.Remove(bundlePath.Name()); err2 != nil {
|
||||||
|
log.Errorf("Failed to remove zip file: %v", err2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
archive := zip.NewWriter(bundlePath)
|
||||||
|
defer func() {
|
||||||
|
if err := archive.Close(); err != nil {
|
||||||
|
log.Errorf("failed to close archive writer: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if status := req.GetStatus(); status != "" {
|
||||||
|
filename := "status.txt"
|
||||||
|
if req.GetAnonymize() {
|
||||||
|
filename = "status.anon.txt"
|
||||||
|
}
|
||||||
|
statusReader := strings.NewReader(status)
|
||||||
|
if err := addFileToZip(archive, statusReader, filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("add status file to zip: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile, err := os.Open(s.logFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open log file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := logFile.Close(); err != nil {
|
||||||
|
log.Errorf("failed to close original log file: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
filename := "client.log.txt"
|
||||||
|
var logReader io.Reader
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
if req.GetAnonymize() {
|
||||||
|
filename = "client.anon.log.txt"
|
||||||
|
var writer io.WriteCloser
|
||||||
|
logReader, writer = io.Pipe()
|
||||||
|
|
||||||
|
go s.anonymize(logFile, writer, errChan)
|
||||||
|
} else {
|
||||||
|
logReader = logFile
|
||||||
|
}
|
||||||
|
if err := addFileToZip(archive, logReader, filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("add log file to zip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.DebugBundleResponse{Path: bundlePath.Name()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) anonymize(reader io.Reader, writer io.WriteCloser, errChan chan<- error) {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses())
|
||||||
|
|
||||||
|
status := s.statusRecorder.GetFullStatus()
|
||||||
|
seedFromStatus(anonymizer, &status)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
log.Errorf("Failed to close writer: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := anonymizer.AnonymizeString(scanner.Text())
|
||||||
|
if _, err := writer.Write([]byte(line + "\n")); err != nil {
|
||||||
|
errChan <- fmt.Errorf("write line to writer: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
errChan <- fmt.Errorf("read line from scanner: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogLevel sets the logging level for the server.
|
||||||
|
func (s *Server) SetLogLevel(_ context.Context, req *proto.SetLogLevelRequest) (*proto.SetLogLevelResponse, error) {
|
||||||
|
level, err := log.ParseLevel(req.Level.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid log level: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetLevel(level)
|
||||||
|
log.Infof("Log level set to %s", level.String())
|
||||||
|
return &proto.SetLogLevelResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFileToZip(archive *zip.Writer, reader io.Reader, filename string) error {
|
||||||
|
header := &zip.FileHeader{
|
||||||
|
Name: filename,
|
||||||
|
Method: zip.Deflate,
|
||||||
|
}
|
||||||
|
|
||||||
|
writer, err := archive.CreateHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create zip file header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(writer, reader); err != nil {
|
||||||
|
return fmt.Errorf("write file to zip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedFromStatus(a *anonymize.Anonymizer, status *peer.FullStatus) {
|
||||||
|
status.ManagementState.URL = a.AnonymizeURI(status.ManagementState.URL)
|
||||||
|
status.SignalState.URL = a.AnonymizeURI(status.SignalState.URL)
|
||||||
|
|
||||||
|
status.LocalPeerState.FQDN = a.AnonymizeDomain(status.LocalPeerState.FQDN)
|
||||||
|
|
||||||
|
for _, peer := range status.Peers {
|
||||||
|
a.AnonymizeDomain(peer.FQDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nsGroup := range status.NSGroupStates {
|
||||||
|
for _, domain := range nsGroup.Domains {
|
||||||
|
a.AnonymizeDomain(domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, relay := range status.Relays {
|
||||||
|
if relay.URI != nil {
|
||||||
|
a.AnonymizeURI(relay.URI.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
client/server/route.go
Normal file
144
client/server/route.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
type selectRoute struct {
|
||||||
|
NetID route.NetID
|
||||||
|
Network netip.Prefix
|
||||||
|
Selected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoutes returns a list of all available routes.
|
||||||
|
func (s *Server) ListRoutes(ctx context.Context, req *proto.ListRoutesRequest) (*proto.ListRoutesResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.connectClient == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := s.connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
routesMap := engine.GetClientRoutesWithNetID()
|
||||||
|
routeSelector := engine.GetRouteManager().GetRouteSelector()
|
||||||
|
|
||||||
|
var routes []*selectRoute
|
||||||
|
for id, rt := range routesMap {
|
||||||
|
if len(rt) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
route := &selectRoute{
|
||||||
|
NetID: id,
|
||||||
|
Network: rt[0].Network,
|
||||||
|
Selected: routeSelector.IsSelected(id),
|
||||||
|
}
|
||||||
|
routes = append(routes, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(routes, func(i, j int) bool {
|
||||||
|
iPrefix := routes[i].Network.Bits()
|
||||||
|
jPrefix := routes[j].Network.Bits()
|
||||||
|
|
||||||
|
if iPrefix == jPrefix {
|
||||||
|
iAddr := routes[i].Network.Addr()
|
||||||
|
jAddr := routes[j].Network.Addr()
|
||||||
|
if iAddr == jAddr {
|
||||||
|
return routes[i].NetID < routes[j].NetID
|
||||||
|
}
|
||||||
|
return iAddr.String() < jAddr.String()
|
||||||
|
}
|
||||||
|
return iPrefix < jPrefix
|
||||||
|
})
|
||||||
|
|
||||||
|
var pbRoutes []*proto.Route
|
||||||
|
for _, route := range routes {
|
||||||
|
pbRoutes = append(pbRoutes, &proto.Route{
|
||||||
|
ID: string(route.NetID),
|
||||||
|
Network: route.Network.String(),
|
||||||
|
Selected: route.Selected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.ListRoutesResponse{
|
||||||
|
Routes: pbRoutes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectRoutes selects specific routes based on the client request.
|
||||||
|
func (s *Server) SelectRoutes(_ context.Context, req *proto.SelectRoutesRequest) (*proto.SelectRoutesResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.connectClient == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := s.connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
routeManager := engine.GetRouteManager()
|
||||||
|
routeSelector := routeManager.GetRouteSelector()
|
||||||
|
if req.GetAll() {
|
||||||
|
routeSelector.SelectAllRoutes()
|
||||||
|
} else {
|
||||||
|
routes := toNetIDs(req.GetRouteIDs())
|
||||||
|
if err := routeSelector.SelectRoutes(routes, req.GetAppend(), maps.Keys(engine.GetClientRoutesWithNetID())); err != nil {
|
||||||
|
return nil, fmt.Errorf("select routes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routeManager.TriggerSelection(engine.GetClientRoutes())
|
||||||
|
|
||||||
|
return &proto.SelectRoutesResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeselectRoutes deselects specific routes based on the client request.
|
||||||
|
func (s *Server) DeselectRoutes(_ context.Context, req *proto.SelectRoutesRequest) (*proto.SelectRoutesResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.connectClient == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := s.connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
routeManager := engine.GetRouteManager()
|
||||||
|
routeSelector := routeManager.GetRouteSelector()
|
||||||
|
if req.GetAll() {
|
||||||
|
routeSelector.DeselectAllRoutes()
|
||||||
|
} else {
|
||||||
|
routes := toNetIDs(req.GetRouteIDs())
|
||||||
|
if err := routeSelector.DeselectRoutes(routes, maps.Keys(engine.GetClientRoutesWithNetID())); err != nil {
|
||||||
|
return nil, fmt.Errorf("deselect routes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routeManager.TriggerSelection(engine.GetClientRoutes())
|
||||||
|
|
||||||
|
return &proto.SelectRoutesResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNetIDs(routes []string) []route.NetID {
|
||||||
|
var netIDs []route.NetID
|
||||||
|
for _, rt := range routes {
|
||||||
|
netIDs = append(netIDs, route.NetID(rt))
|
||||||
|
}
|
||||||
|
return netIDs
|
||||||
|
}
|
||||||
@@ -15,15 +15,15 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
@@ -57,6 +57,8 @@ type Server struct {
|
|||||||
config *internal.Config
|
config *internal.Config
|
||||||
proto.UnimplementedDaemonServiceServer
|
proto.UnimplementedDaemonServiceServer
|
||||||
|
|
||||||
|
connectClient *internal.ConnectClient
|
||||||
|
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
sessionWatcher *internal.SessionWatcher
|
sessionWatcher *internal.SessionWatcher
|
||||||
|
|
||||||
@@ -182,7 +184,8 @@ func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Conf
|
|||||||
|
|
||||||
runOperation := func() error {
|
runOperation := func() error {
|
||||||
log.Tracef("running client connection")
|
log.Tracef("running client connection")
|
||||||
err := internal.RunClientWithProbes(ctx, config, statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
|
s.connectClient = internal.NewConnectClient(ctx, config, statusRecorder)
|
||||||
|
err := s.connectClient.RunWithProbes(mgmProbe, signalProbe, relayProbe, wgProbe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
||||||
}
|
}
|
||||||
@@ -352,6 +355,11 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
s.latestConfigInput.WireguardPort = &port
|
s.latestConfigInput.WireguardPort = &port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if msg.NetworkMonitor != nil {
|
||||||
|
inputConfig.NetworkMonitor = msg.NetworkMonitor
|
||||||
|
s.latestConfigInput.NetworkMonitor = msg.NetworkMonitor
|
||||||
|
}
|
||||||
|
|
||||||
if len(msg.ExtraIFaceBlacklist) > 0 {
|
if len(msg.ExtraIFaceBlacklist) > 0 {
|
||||||
inputConfig.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
inputConfig.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
||||||
s.latestConfigInput.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
s.latestConfigInput.ExtraIFaceBlackList = msg.ExtraIFaceBlacklist
|
||||||
@@ -661,7 +669,6 @@ func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto
|
|||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) onSessionExpire() {
|
func (s *Server) onSessionExpire() {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
isUIActive := internal.CheckUIApp()
|
isUIActive := internal.CheckUIApp()
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
@@ -105,10 +106,11 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
store, err := server.NewStoreFromJson(config.Datadir, nil)
|
store, cleanUp, err := server.NewTestStoreFromJson(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
//go:build !(linux && 386)
|
//go:build !(linux && 386)
|
||||||
// +build !linux !386
|
|
||||||
|
|
||||||
// skipping linux 32 bits build and tests
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -58,14 +56,23 @@ func main() {
|
|||||||
|
|
||||||
var showSettings bool
|
var showSettings bool
|
||||||
flag.BoolVar(&showSettings, "settings", false, "run settings windows")
|
flag.BoolVar(&showSettings, "settings", false, "run settings windows")
|
||||||
|
var showRoutes bool
|
||||||
|
flag.BoolVar(&showRoutes, "routes", false, "run routes windows")
|
||||||
|
var errorMSG string
|
||||||
|
flag.StringVar(&errorMSG, "error-msg", "", "displays a error message window")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
a := app.NewWithID("NetBird")
|
a := app.NewWithID("NetBird")
|
||||||
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
|
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
|
||||||
|
|
||||||
client := newServiceClient(daemonAddr, a, showSettings)
|
if errorMSG != "" {
|
||||||
if showSettings {
|
showErrorMSG(errorMSG)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := newServiceClient(daemonAddr, a, showSettings, showRoutes)
|
||||||
|
if showSettings || showRoutes {
|
||||||
a.Run()
|
a.Run()
|
||||||
} else {
|
} else {
|
||||||
if err := checkPIDFile(); err != nil {
|
if err := checkPIDFile(); err != nil {
|
||||||
@@ -128,6 +135,7 @@ type serviceClient struct {
|
|||||||
mVersionDaemon *systray.MenuItem
|
mVersionDaemon *systray.MenuItem
|
||||||
mUpdate *systray.MenuItem
|
mUpdate *systray.MenuItem
|
||||||
mQuit *systray.MenuItem
|
mQuit *systray.MenuItem
|
||||||
|
mRoutes *systray.MenuItem
|
||||||
|
|
||||||
// application with main windows.
|
// application with main windows.
|
||||||
app fyne.App
|
app fyne.App
|
||||||
@@ -152,12 +160,15 @@ type serviceClient struct {
|
|||||||
daemonVersion string
|
daemonVersion string
|
||||||
updateIndicationLock sync.Mutex
|
updateIndicationLock sync.Mutex
|
||||||
isUpdateIconActive bool
|
isUpdateIconActive bool
|
||||||
|
|
||||||
|
showRoutes bool
|
||||||
|
wRoutes fyne.Window
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServiceClient instance constructor
|
// newServiceClient instance constructor
|
||||||
//
|
//
|
||||||
// This constructor also builds the UI elements for the settings window.
|
// This constructor also builds the UI elements for the settings window.
|
||||||
func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient {
|
func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes bool) *serviceClient {
|
||||||
s := &serviceClient{
|
s := &serviceClient{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
addr: addr,
|
addr: addr,
|
||||||
@@ -165,6 +176,7 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient
|
|||||||
sendNotification: false,
|
sendNotification: false,
|
||||||
|
|
||||||
showSettings: showSettings,
|
showSettings: showSettings,
|
||||||
|
showRoutes: showRoutes,
|
||||||
update: version.NewUpdate(),
|
update: version.NewUpdate(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,14 +196,16 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
if showSettings {
|
if showSettings {
|
||||||
s.showUIElements()
|
s.showSettingsUI()
|
||||||
return s
|
return s
|
||||||
|
} else if showRoutes {
|
||||||
|
s.showRoutesUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) showUIElements() {
|
func (s *serviceClient) showSettingsUI() {
|
||||||
// add settings window UI elements.
|
// add settings window UI elements.
|
||||||
s.wSettings = s.app.NewWindow("NetBird Settings")
|
s.wSettings = s.app.NewWindow("NetBird Settings")
|
||||||
s.iMngURL = widget.NewEntry()
|
s.iMngURL = widget.NewEntry()
|
||||||
@@ -209,6 +223,18 @@ func (s *serviceClient) showUIElements() {
|
|||||||
s.wSettings.Show()
|
s.wSettings.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// showErrorMSG opens a fyne app window to display the supplied message
|
||||||
|
func showErrorMSG(msg string) {
|
||||||
|
app := app.New()
|
||||||
|
w := app.NewWindow("NetBird Error")
|
||||||
|
content := widget.NewLabel(msg)
|
||||||
|
content.Wrapping = fyne.TextWrapWord
|
||||||
|
w.SetContent(content)
|
||||||
|
w.Resize(fyne.NewSize(400, 100))
|
||||||
|
w.Show()
|
||||||
|
app.Run()
|
||||||
|
}
|
||||||
|
|
||||||
// getSettingsForm to embed it into settings window.
|
// getSettingsForm to embed it into settings window.
|
||||||
func (s *serviceClient) getSettingsForm() *widget.Form {
|
func (s *serviceClient) getSettingsForm() *widget.Form {
|
||||||
return &widget.Form{
|
return &widget.Form{
|
||||||
@@ -373,6 +399,7 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
|
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("get service status: %v", err)
|
log.Errorf("get service status: %v", err)
|
||||||
|
s.setDisconnectedStatus()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,18 +424,10 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
s.mStatus.SetTitle("Connected")
|
s.mStatus.SetTitle("Connected")
|
||||||
s.mUp.Disable()
|
s.mUp.Disable()
|
||||||
s.mDown.Enable()
|
s.mDown.Enable()
|
||||||
|
s.mRoutes.Enable()
|
||||||
systrayIconState = true
|
systrayIconState = true
|
||||||
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
||||||
s.connected = false
|
s.setDisconnectedStatus()
|
||||||
if s.isUpdateIconActive {
|
|
||||||
systray.SetIcon(s.icUpdateDisconnected)
|
|
||||||
} else {
|
|
||||||
systray.SetIcon(s.icDisconnected)
|
|
||||||
}
|
|
||||||
systray.SetTooltip("NetBird (Disconnected)")
|
|
||||||
s.mStatus.SetTitle("Disconnected")
|
|
||||||
s.mDown.Disable()
|
|
||||||
s.mUp.Enable()
|
|
||||||
systrayIconState = false
|
systrayIconState = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +472,20 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) setDisconnectedStatus() {
|
||||||
|
s.connected = false
|
||||||
|
if s.isUpdateIconActive {
|
||||||
|
systray.SetIcon(s.icUpdateDisconnected)
|
||||||
|
} else {
|
||||||
|
systray.SetIcon(s.icDisconnected)
|
||||||
|
}
|
||||||
|
systray.SetTooltip("NetBird (Disconnected)")
|
||||||
|
s.mStatus.SetTitle("Disconnected")
|
||||||
|
s.mDown.Disable()
|
||||||
|
s.mUp.Enable()
|
||||||
|
s.mRoutes.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) onTrayReady() {
|
func (s *serviceClient) onTrayReady() {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetIcon(s.icDisconnected)
|
||||||
systray.SetTooltip("NetBird")
|
systray.SetTooltip("NetBird")
|
||||||
@@ -464,9 +497,11 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
s.mUp = systray.AddMenuItem("Connect", "Connect")
|
s.mUp = systray.AddMenuItem("Connect", "Connect")
|
||||||
s.mDown = systray.AddMenuItem("Disconnect", "Disconnect")
|
s.mDown = systray.AddMenuItem("Disconnect", "Disconnect")
|
||||||
s.mDown.Disable()
|
s.mDown.Disable()
|
||||||
s.mAdminPanel = systray.AddMenuItem("Admin Panel", "Wiretrustee Admin Panel")
|
s.mAdminPanel = systray.AddMenuItem("Admin Panel", "Netbird Admin Panel")
|
||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
s.mSettings = systray.AddMenuItem("Settings", "Settings of the application")
|
s.mSettings = systray.AddMenuItem("Settings", "Settings of the application")
|
||||||
|
s.mRoutes = systray.AddMenuItem("Network Routes", "Open the routes management window")
|
||||||
|
s.mRoutes.Disable()
|
||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
|
|
||||||
s.mAbout = systray.AddMenuItem("About", "About")
|
s.mAbout = systray.AddMenuItem("About", "About")
|
||||||
@@ -504,16 +539,22 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
case <-s.mAdminPanel.ClickedCh:
|
case <-s.mAdminPanel.ClickedCh:
|
||||||
err = open.Run(s.adminURL)
|
err = open.Run(s.adminURL)
|
||||||
case <-s.mUp.ClickedCh:
|
case <-s.mUp.ClickedCh:
|
||||||
|
s.mUp.Disabled()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer s.mUp.Enable()
|
||||||
err := s.menuUpClick()
|
err := s.menuUpClick()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.runSelfCommand("error-msg", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
case <-s.mDown.ClickedCh:
|
case <-s.mDown.ClickedCh:
|
||||||
|
s.mDown.Disable()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer s.mDown.Enable()
|
||||||
err := s.menuDownClick()
|
err := s.menuDownClick()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.runSelfCommand("error-msg", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -521,24 +562,8 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
s.mSettings.Disable()
|
s.mSettings.Disable()
|
||||||
go func() {
|
go func() {
|
||||||
defer s.mSettings.Enable()
|
defer s.mSettings.Enable()
|
||||||
proc, err := os.Executable()
|
defer s.getSrvConfig()
|
||||||
if err != nil {
|
s.runSelfCommand("settings", "true")
|
||||||
log.Errorf("show settings: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(proc, "--settings=true")
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
|
||||||
log.Errorf("start settings UI: %v, %s", err, string(out))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(out) != 0 {
|
|
||||||
log.Info("settings change:", string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// update config in systray when settings windows closed
|
|
||||||
s.getSrvConfig()
|
|
||||||
}()
|
}()
|
||||||
case <-s.mQuit.ClickedCh:
|
case <-s.mQuit.ClickedCh:
|
||||||
systray.Quit()
|
systray.Quit()
|
||||||
@@ -548,6 +573,12 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%s", err)
|
log.Errorf("%s", err)
|
||||||
}
|
}
|
||||||
|
case <-s.mRoutes.ClickedCh:
|
||||||
|
s.mRoutes.Disable()
|
||||||
|
go func() {
|
||||||
|
defer s.mRoutes.Enable()
|
||||||
|
s.runSelfCommand("routes", "true")
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("process connection: %v", err)
|
log.Errorf("process connection: %v", err)
|
||||||
@@ -556,6 +587,24 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) runSelfCommand(command, arg string) {
|
||||||
|
proc, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("show %s failed with error: %v", command, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(proc, fmt.Sprintf("--%s=%s", command, arg))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
log.Errorf("start %s UI: %v, %s", command, err, string(out))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(out) != 0 {
|
||||||
|
log.Infof("command %s executed: %s", command, string(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func normalizedVersion(version string) string {
|
func normalizedVersion(version string) string {
|
||||||
versionString := version
|
versionString := version
|
||||||
if unicode.IsDigit(rune(versionString[0])) {
|
if unicode.IsDigit(rune(versionString[0])) {
|
||||||
|
|||||||
203
client/ui/route.go
Normal file
203
client/ui/route.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
//go:build !(linux && 386)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *serviceClient) showRoutesUI() {
|
||||||
|
s.wRoutes = s.app.NewWindow("NetBird Routes")
|
||||||
|
|
||||||
|
grid := container.New(layout.NewGridLayout(2))
|
||||||
|
go s.updateRoutes(grid)
|
||||||
|
routeCheckContainer := container.NewVBox()
|
||||||
|
routeCheckContainer.Add(grid)
|
||||||
|
scrollContainer := container.NewVScroll(routeCheckContainer)
|
||||||
|
scrollContainer.SetMinSize(fyne.NewSize(200, 300))
|
||||||
|
|
||||||
|
buttonBox := container.NewHBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
widget.NewButton("Refresh", func() {
|
||||||
|
s.updateRoutes(grid)
|
||||||
|
}),
|
||||||
|
widget.NewButton("Select all", func() {
|
||||||
|
s.selectAllRoutes()
|
||||||
|
s.updateRoutes(grid)
|
||||||
|
}),
|
||||||
|
widget.NewButton("Deselect All", func() {
|
||||||
|
s.deselectAllRoutes()
|
||||||
|
s.updateRoutes(grid)
|
||||||
|
}),
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
content := container.NewBorder(nil, buttonBox, nil, nil, scrollContainer)
|
||||||
|
|
||||||
|
s.wRoutes.SetContent(content)
|
||||||
|
s.wRoutes.Show()
|
||||||
|
|
||||||
|
s.startAutoRefresh(5*time.Second, grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) updateRoutes(grid *fyne.Container) {
|
||||||
|
routes, err := s.fetchRoutes()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get client: %v", err)
|
||||||
|
s.showError(fmt.Errorf("get client: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.Objects = nil
|
||||||
|
idHeader := widget.NewLabelWithStyle(" ID", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||||
|
networkHeader := widget.NewLabelWithStyle("Network", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||||
|
|
||||||
|
grid.Add(idHeader)
|
||||||
|
grid.Add(networkHeader)
|
||||||
|
for _, route := range routes {
|
||||||
|
r := route
|
||||||
|
|
||||||
|
checkBox := widget.NewCheck(r.ID, func(checked bool) {
|
||||||
|
s.selectRoute(r.ID, checked)
|
||||||
|
})
|
||||||
|
checkBox.Checked = route.Selected
|
||||||
|
checkBox.Resize(fyne.NewSize(20, 20))
|
||||||
|
checkBox.Refresh()
|
||||||
|
|
||||||
|
grid.Add(checkBox)
|
||||||
|
grid.Add(widget.NewLabel(r.Network))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wRoutes.Content().Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) fetchRoutes() ([]*proto.Route, error) {
|
||||||
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conn.ListRoutes(s.ctx, &proto.ListRoutesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list routes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) selectRoute(id string, checked bool) {
|
||||||
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get client: %v", err)
|
||||||
|
s.showError(fmt.Errorf("get client: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &proto.SelectRoutesRequest{
|
||||||
|
RouteIDs: []string{id},
|
||||||
|
Append: checked,
|
||||||
|
}
|
||||||
|
|
||||||
|
if checked {
|
||||||
|
if _, err := conn.SelectRoutes(s.ctx, req); err != nil {
|
||||||
|
log.Errorf("failed to select route: %v", err)
|
||||||
|
s.showError(fmt.Errorf("failed to select route: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("Route %s selected", id)
|
||||||
|
} else {
|
||||||
|
if _, err := conn.DeselectRoutes(s.ctx, req); err != nil {
|
||||||
|
log.Errorf("failed to deselect route: %v", err)
|
||||||
|
s.showError(fmt.Errorf("failed to deselect route: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("Route %s deselected", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) selectAllRoutes() {
|
||||||
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get client: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &proto.SelectRoutesRequest{
|
||||||
|
All: true,
|
||||||
|
}
|
||||||
|
if _, err := conn.SelectRoutes(s.ctx, req); err != nil {
|
||||||
|
log.Errorf("failed to select all routes: %v", err)
|
||||||
|
s.showError(fmt.Errorf("failed to select all routes: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("All routes selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) deselectAllRoutes() {
|
||||||
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get client: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &proto.SelectRoutesRequest{
|
||||||
|
All: true,
|
||||||
|
}
|
||||||
|
if _, err := conn.DeselectRoutes(s.ctx, req); err != nil {
|
||||||
|
log.Errorf("failed to deselect all routes: %v", err)
|
||||||
|
s.showError(fmt.Errorf("failed to deselect all routes: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("All routes deselected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) showError(err error) {
|
||||||
|
wrappedMessage := wrapText(err.Error(), 50)
|
||||||
|
|
||||||
|
dialog.ShowError(fmt.Errorf("%s", wrappedMessage), s.wRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) startAutoRefresh(interval time.Duration, grid *fyne.Container) {
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
go func() {
|
||||||
|
for range ticker.C {
|
||||||
|
s.updateRoutes(grid)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.wRoutes.SetOnClosed(func() {
|
||||||
|
ticker.Stop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapText inserts newlines into the text to ensure that each line is
|
||||||
|
// no longer than 'lineLength' runes.
|
||||||
|
func wrapText(text string, lineLength int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
var currentLineLength int
|
||||||
|
|
||||||
|
for _, runeValue := range text {
|
||||||
|
sb.WriteRune(runeValue)
|
||||||
|
currentLineLength++
|
||||||
|
|
||||||
|
if currentLineLength >= lineLength || runeValue == '\n' {
|
||||||
|
sb.WriteRune('\n')
|
||||||
|
currentLineLength = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
44
go.mod
44
go.mod
@@ -6,7 +6,7 @@ toolchain go1.21.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cunicu.li/go-rosenpass v0.4.0
|
cunicu.li/go-rosenpass v0.4.0
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3
|
github.com/cenkalti/backoff/v4 v4.2.0
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.3
|
||||||
@@ -21,8 +21,8 @@ require (
|
|||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||||
golang.org/x/crypto v0.18.0
|
golang.org/x/crypto v0.21.0
|
||||||
golang.org/x/sys v0.16.0
|
golang.org/x/sys v0.18.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
@@ -54,7 +54,7 @@ require (
|
|||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/libp2p/go-netroute v0.2.1
|
github.com/libp2p/go-netroute v0.2.1
|
||||||
github.com/magiconair/properties v1.8.5
|
github.com/magiconair/properties v1.8.7
|
||||||
github.com/mattn/go-sqlite3 v1.14.19
|
github.com/mattn/go-sqlite3 v1.14.19
|
||||||
github.com/mdlayher/socket v0.4.1
|
github.com/mdlayher/socket v0.4.1
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
@@ -72,6 +72,8 @@ 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.4
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/testcontainers/testcontainers-go v0.20.0
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.20.0
|
||||||
github.com/things-go/go-socks5 v0.0.4
|
github.com/things-go/go-socks5 v0.0.4
|
||||||
github.com/yusufpapurcu/wmi v1.2.3
|
github.com/yusufpapurcu/wmi v1.2.3
|
||||||
github.com/zcalusic/sysinfo v1.0.2
|
github.com/zcalusic/sysinfo v1.0.2
|
||||||
@@ -82,28 +84,37 @@ require (
|
|||||||
goauthentik.io/api/v3 v3.2023051.3
|
goauthentik.io/api/v3 v3.2023051.3
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
|
||||||
golang.org/x/net v0.20.0
|
golang.org/x/net v0.23.0
|
||||||
golang.org/x/oauth2 v0.8.0
|
golang.org/x/oauth2 v0.8.0
|
||||||
golang.org/x/sync v0.3.0
|
golang.org/x/sync v0.3.0
|
||||||
golang.org/x/term v0.16.0
|
golang.org/x/term v0.18.0
|
||||||
google.golang.org/api v0.126.0
|
google.golang.org/api v0.126.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gorm.io/driver/postgres v1.5.7
|
||||||
gorm.io/driver/sqlite v1.5.3
|
gorm.io/driver/sqlite v1.5.3
|
||||||
gorm.io/gorm v1.25.4
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.19.3 // indirect
|
cloud.google.com/go/compute v1.19.3 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.0 // 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.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/containerd/containerd v1.6.19 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.1 // 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
|
||||||
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
|
github.com/docker/docker v23.0.5+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
|
||||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||||
@@ -118,29 +129,43 @@ require (
|
|||||||
github.com/go-ole/go-ole v1.2.6 // 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/gogo/protobuf v1.3.2 // 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/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/google/s2a-go v0.1.4 // indirect
|
github.com/google/s2a-go v0.1.4 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.15.9 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
|
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||||
|
github.com/opencontainers/runc v1.1.5 // indirect
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||||
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
||||||
github.com/pion/dtls/v2 v2.2.10 // indirect
|
github.com/pion/dtls/v2 v2.2.10 // indirect
|
||||||
github.com/pion/mdns v0.0.12 // indirect
|
github.com/pion/mdns v0.0.12 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/transport/v2 v2.2.4 // indirect
|
github.com/pion/transport/v2 v2.2.4 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
@@ -154,12 +179,13 @@ require (
|
|||||||
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.10.0 // indirect
|
golang.org/x/image v0.10.0 // indirect
|
||||||
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||||
|
golang.org/x/tools v0.13.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/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // 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/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
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
||||||
|
|||||||
117
go.sum
117
go.sum
@@ -40,12 +40,18 @@ cunicu.li/go-rosenpass v0.4.0/go.mod h1:MPbjH9nxV4l3vEagKVdFNwHOketqgS5/To1VYJpl
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
|
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
|
||||||
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
|
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||||
|
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||||
|
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||||
|
github.com/Microsoft/hcsshim v0.9.7 h1:mKNHW/Xvv1aFH87Jb6ERDzXTJTLPlmzfZ28VBFD/bfg=
|
||||||
|
github.com/Microsoft/hcsshim v0.9.7/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc=
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
@@ -73,16 +79,18 @@ github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQ
|
|||||||
github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
|
github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
|
||||||
github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
|
github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
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/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/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 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||||
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=
|
||||||
|
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||||
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
@@ -92,16 +100,25 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
|
|||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/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-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
|
github.com/containerd/containerd v1.6.19 h1:F0qgQPrG0P2JPgwpxWxYavrVeXAG0ezUIB9Z/4FTUAU=
|
||||||
|
github.com/containerd/containerd v1.6.19/go.mod h1:HZCDMn4v/Xl2579/MvtOC2M206i+JJ6VxFWU/NetrGY=
|
||||||
|
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
||||||
|
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
|
||||||
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
|
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
|
||||||
github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
|
github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
|
||||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 h1:/DS5cDX3FJdl+XaN2D7XAwFpuanTxnp52DBLZAaJKx0=
|
github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 h1:/DS5cDX3FJdl+XaN2D7XAwFpuanTxnp52DBLZAaJKx0=
|
||||||
github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
|
github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -111,6 +128,15 @@ github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkz
|
|||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||||
|
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k=
|
||||||
|
github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
@@ -127,6 +153,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
|||||||
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=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
||||||
@@ -188,10 +215,13 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
|||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
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=
|
||||||
@@ -290,8 +320,9 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
|
|||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo=
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||||
@@ -305,8 +336,16 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
|||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
@@ -332,7 +371,10 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
|||||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||||
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
@@ -345,11 +387,13 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
||||||
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
|
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
|
||||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||||
@@ -369,12 +413,22 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE9
|
|||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
|
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
|
||||||
|
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
|
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||||
|
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||||
|
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||||
|
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f h1:J/7hjLaHLD7epG0m6TBMGmp4NQ+ibBYLfeyJWdAIFLA=
|
||||||
|
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f/go.mod h1:15ce4BGCFxt7I5NQKT+HV0yEDxmf6fSysfEDiVo3zFM=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
@@ -414,6 +468,14 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||||
|
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||||
|
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||||
|
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
|
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||||
@@ -422,6 +484,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
|
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
|
||||||
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
|
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
|
||||||
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
@@ -485,10 +548,12 @@ github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
|||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||||
@@ -526,13 +591,21 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.20.0 h1:ASrcJee7vcWNw43yUgL2n8KA5IOywrF031GawlrkVkE=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.20.0/go.mod h1:zb+NOlCQBkZ7RQp4QI+YMIHyO2CQ/qsXzNF5eLJ24SY=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.20.0 h1:skGd0Tv6USw6c9aJwea+Mb2WonLqf6N5npbS5WxbGQ0=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.20.0/go.mod h1:wtdaiIzG+DlZ/0DbNvrJ89TT7RUer8ZnRcv4y+xHcU8=
|
||||||
github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0=
|
github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0=
|
||||||
github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ=
|
github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
@@ -581,8 +654,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
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=
|
||||||
@@ -622,6 +696,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -657,6 +733,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
@@ -669,8 +746,9 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -707,6 +785,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -716,6 +795,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -750,6 +831,9 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -761,16 +845,18 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||||
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -830,14 +916,18 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
|
|||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -940,6 +1030,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
@@ -978,10 +1069,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||||
|
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||||
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||||
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
|
||||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||||
|
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package iface
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -79,8 +80,19 @@ func TestWGIface_UpdateAddr(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, addr, addrs[0].String())
|
var found bool
|
||||||
|
for _, a := range addrs {
|
||||||
|
prefix, err := netip.ParsePrefix(a.String())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if prefix.Addr().Is4() {
|
||||||
|
found = true
|
||||||
|
assert.Equal(t, addr, prefix.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("v4 address not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIfaceAddrs(ifaceName string) ([]net.Addr, error) {
|
func getIfaceAddrs(ifaceName string) ([]net.Addr, error) {
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ package iface
|
|||||||
type TunAdapter interface {
|
type TunAdapter interface {
|
||||||
ConfigureInterface(address string, mtu int, dns string, searchDomains string, routes string) (int, error)
|
ConfigureInterface(address string, mtu int, dns string, searchDomains string, routes string) (int, error)
|
||||||
UpdateAddr(address string) error
|
UpdateAddr(address string) error
|
||||||
|
ProtectSocket(fd int32) bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !ios
|
//go:build !ios
|
||||||
// +build !ios
|
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
@@ -121,13 +120,19 @@ func (t *tunDevice) Wrapper() *DeviceWrapper {
|
|||||||
func (t *tunDevice) assignAddr() error {
|
func (t *tunDevice) assignAddr() error {
|
||||||
cmd := exec.Command("ifconfig", t.name, "inet", t.address.IP.String(), t.address.IP.String())
|
cmd := exec.Command("ifconfig", t.name, "inet", t.address.IP.String(), t.address.IP.String())
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
log.Infof(`adding address command "%v" failed with output %s and error: `, cmd.String(), out)
|
log.Errorf("adding address command '%v' failed with output: %s", cmd.String(), out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dummy ipv6 so routing works
|
||||||
|
cmd = exec.Command("ifconfig", t.name, "inet6", "fe80::/64")
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Debugf("adding address command '%v' failed with output: %s", cmd.String(), out)
|
||||||
|
}
|
||||||
|
|
||||||
routeCmd := exec.Command("route", "add", "-net", t.address.Network.String(), "-interface", t.name)
|
routeCmd := exec.Command("route", "add", "-net", t.address.Network.String(), "-interface", t.name)
|
||||||
if out, err := routeCmd.CombinedOutput(); err != nil {
|
if out, err := routeCmd.CombinedOutput(); err != nil {
|
||||||
log.Printf(`adding route command "%v" failed with output %s and error: `, routeCmd.String(), out)
|
log.Errorf("adding route command '%v' failed with output: %s", routeCmd.String(), out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -132,7 +132,13 @@ func (c *wgUSPConfigurer) removeAllowedIP(peerKey string, ip string) error {
|
|||||||
|
|
||||||
lines := strings.Split(ipc, "\n")
|
lines := strings.Split(ipc, "\n")
|
||||||
|
|
||||||
output := ""
|
peer := wgtypes.PeerConfig{
|
||||||
|
PublicKey: peerKeyParsed,
|
||||||
|
UpdateOnly: true,
|
||||||
|
ReplaceAllowedIPs: true,
|
||||||
|
AllowedIPs: []net.IPNet{},
|
||||||
|
}
|
||||||
|
|
||||||
foundPeer := false
|
foundPeer := false
|
||||||
removedAllowedIP := false
|
removedAllowedIP := false
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@@ -156,19 +162,23 @@ func (c *wgUSPConfigurer) removeAllowedIP(peerKey string, ip string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append the line to the output string
|
// Append the line to the output string
|
||||||
if strings.HasPrefix(line, "private_key=") || strings.HasPrefix(line, "listen_port=") ||
|
if foundPeer && strings.HasPrefix(line, "allowed_ip=") {
|
||||||
strings.HasPrefix(line, "public_key=") || strings.HasPrefix(line, "preshared_key=") ||
|
allowedIP := strings.TrimPrefix(line, "allowed_ip=")
|
||||||
strings.HasPrefix(line, "endpoint=") || strings.HasPrefix(line, "persistent_keepalive_interval=") ||
|
_, ipNet, err := net.ParseCIDR(allowedIP)
|
||||||
strings.HasPrefix(line, "allowed_ip=") {
|
if err != nil {
|
||||||
output += line + "\n"
|
return err
|
||||||
|
}
|
||||||
|
peer.AllowedIPs = append(peer.AllowedIPs, *ipNet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !removedAllowedIP {
|
if !removedAllowedIP {
|
||||||
return fmt.Errorf("allowedIP not found")
|
return fmt.Errorf("allowedIP not found")
|
||||||
} else {
|
|
||||||
return c.device.IpcSet(output)
|
|
||||||
}
|
}
|
||||||
|
config := wgtypes.Config{
|
||||||
|
Peers: []wgtypes.PeerConfig{peer},
|
||||||
|
}
|
||||||
|
return c.device.IpcSet(toWgUserspaceString(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
// startUAPI starts the UAPI listener for managing the WireGuard interface via external tool
|
// startUAPI starts the UAPI listener for managing the WireGuard interface via external tool
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
io.Closer
|
io.Closer
|
||||||
Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
Sync(ctx context.Context, msgHandler func(msg *proto.SyncResponse) error) error
|
||||||
GetServerPublicKey() (*wgtypes.Key, error)
|
GetServerPublicKey() (*wgtypes.Key, error)
|
||||||
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)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
mgmt "github.com/netbirdio/netbird/management/server"
|
mgmt "github.com/netbirdio/netbird/management/server"
|
||||||
@@ -61,10 +62,11 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
store, err := mgmt.NewStoreFromJson(config.Datadir, nil)
|
store, cleanUp, err := mgmt.NewTestStoreFromJson(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
@@ -255,7 +257,7 @@ func TestClient_Sync(t *testing.T) {
|
|||||||
ch := make(chan *mgmtProto.SyncResponse, 1)
|
ch := make(chan *mgmtProto.SyncResponse, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err = client.Sync(func(msg *mgmtProto.SyncResponse) error {
|
err = client.Sync(context.Background(), func(msg *mgmtProto.SyncResponse) error {
|
||||||
ch <- msg
|
ch <- msg
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ func (c *GrpcClient) ready() bool {
|
|||||||
|
|
||||||
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
||||||
// Blocking request. The result will be sent via msgHandler callback function
|
// Blocking request. The result will be sent via msgHandler callback function
|
||||||
func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
func (c *GrpcClient) Sync(ctx context.Context, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||||
backOff := defaultBackoff(c.ctx)
|
backOff := defaultBackoff(ctx)
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
log.Debugf("management connection state %v", c.conn.GetState())
|
log.Debugf("management connection state %v", c.conn.GetState())
|
||||||
@@ -123,7 +123,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
if connState == connectivity.Shutdown {
|
if connState == connectivity.Shutdown {
|
||||||
return backoff.Permanent(fmt.Errorf("connection to management has been shut down"))
|
return backoff.Permanent(fmt.Errorf("connection to management has been shut down"))
|
||||||
} else if !(connState == connectivity.Ready || connState == connectivity.Idle) {
|
} else if !(connState == connectivity.Ready || connState == connectivity.Idle) {
|
||||||
c.conn.WaitForStateChange(c.ctx, connState)
|
c.conn.WaitForStateChange(ctx, connState)
|
||||||
return fmt.Errorf("connection to management is not ready and in %s state", connState)
|
return fmt.Errorf("connection to management is not ready and in %s state", connState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancelStream := context.WithCancel(c.ctx)
|
ctx, cancelStream := context.WithCancel(ctx)
|
||||||
defer cancelStream()
|
defer cancelStream()
|
||||||
stream, err := c.connectToStream(ctx, *serverPubKey)
|
stream, err := c.connectToStream(ctx, *serverPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -276,7 +276,8 @@ func (c *GrpcClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||||
|
return nil, fmt.Errorf("failed while getting Management Service public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
serverKey, err := wgtypes.ParseKey(resp.Key)
|
serverKey, err := wgtypes.ParseKey(resp.Key)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
@@ -9,7 +11,7 @@ import (
|
|||||||
|
|
||||||
type MockClient struct {
|
type MockClient struct {
|
||||||
CloseFunc func() error
|
CloseFunc func() error
|
||||||
SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error
|
SyncFunc func(ctx context.Context, msgHandler func(msg *proto.SyncResponse) error) error
|
||||||
GetServerPublicKeyFunc func() (*wgtypes.Key, error)
|
GetServerPublicKeyFunc func() (*wgtypes.Key, error)
|
||||||
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
@@ -28,11 +30,11 @@ func (m *MockClient) Close() error {
|
|||||||
return m.CloseFunc()
|
return m.CloseFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
func (m *MockClient) Sync(ctx context.Context, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||||
if m.SyncFunc == nil {
|
if m.SyncFunc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return m.SyncFunc(msgHandler)
|
return m.SyncFunc(ctx, msgHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
func (m *MockClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/util"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shortDown = "Rollback SQLite store to JSON file store. Please make a backup of the SQLite file before running this command."
|
var shortDown = "Rollback SQLite store to JSON file store. Please make a backup of the SQLite file before running this command."
|
||||||
@@ -39,16 +40,16 @@ var downCmd = &cobra.Command{
|
|||||||
return fmt.Errorf("%s already exists, couldn't continue the operation", fileStorePath)
|
return fmt.Errorf("%s already exists, couldn't continue the operation", fileStorePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlstore, err := server.NewSqliteStore(mgmtDataDir, nil)
|
sqlStore, err := server.NewSqliteStore(mgmtDataDir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sqliteStoreAccounts := len(sqlstore.GetAllAccounts())
|
sqliteStoreAccounts := len(sqlStore.GetAllAccounts())
|
||||||
log.Infof("%d account will be migrated from sqlite store %s to file store %s",
|
log.Infof("%d account will be migrated from sqlite store %s to file store %s",
|
||||||
sqliteStoreAccounts, sqliteStorePath, fileStorePath)
|
sqliteStoreAccounts, sqliteStorePath, fileStorePath)
|
||||||
|
|
||||||
store, err := server.NewFilestoreFromSqliteStore(sqlstore, mgmtDataDir, nil)
|
store, err := server.NewFilestoreFromSqliteStore(sqlStore, mgmtDataDir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ const (
|
|||||||
DefaultPeerLoginExpiration = 24 * time.Hour
|
DefaultPeerLoginExpiration = 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type userLoggedInOnce bool
|
||||||
|
|
||||||
type ExternalCacheManager cache.CacheInterface[*idp.UserData]
|
type ExternalCacheManager cache.CacheInterface[*idp.UserData]
|
||||||
|
|
||||||
func cacheEntryExpiration() time.Duration {
|
func cacheEntryExpiration() time.Duration {
|
||||||
@@ -74,7 +76,7 @@ type AccountManager interface {
|
|||||||
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
||||||
ListUsers(accountID string) ([]*User, error)
|
ListUsers(accountID string) ([]*User, error)
|
||||||
GetPeers(accountID, userID string) ([]*nbpeer.Peer, error)
|
GetPeers(accountID, userID string) ([]*nbpeer.Peer, error)
|
||||||
MarkPeerConnected(peerKey string, connected bool, realIP net.IP) error
|
MarkPeerConnected(peerKey string, connected bool, realIP net.IP, account *Account) error
|
||||||
DeletePeer(accountID, peerID, userID string) error
|
DeletePeer(accountID, peerID, userID string) error
|
||||||
UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error)
|
UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error)
|
||||||
GetNetworkMap(peerID string) (*NetworkMap, error)
|
GetNetworkMap(peerID string) (*NetworkMap, error)
|
||||||
@@ -98,10 +100,10 @@ type AccountManager interface {
|
|||||||
SavePolicy(accountID, userID string, policy *Policy) error
|
SavePolicy(accountID, userID string, policy *Policy) error
|
||||||
DeletePolicy(accountID, policyID, userID string) error
|
DeletePolicy(accountID, policyID, userID string) error
|
||||||
ListPolicies(accountID, userID string) ([]*Policy, error)
|
ListPolicies(accountID, userID string) ([]*Policy, error)
|
||||||
GetRoute(accountID, routeID, userID string) (*route.Route, error)
|
GetRoute(accountID string, routeID route.ID, userID string) (*route.Route, error)
|
||||||
CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||||
SaveRoute(accountID, userID string, route *route.Route) error
|
SaveRoute(accountID, userID string, route *route.Route) error
|
||||||
DeleteRoute(accountID, routeID, userID string) error
|
DeleteRoute(accountID string, routeID route.ID, userID string) error
|
||||||
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
||||||
GetNameServerGroup(accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
GetNameServerGroup(accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||||
CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error)
|
CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error)
|
||||||
@@ -115,8 +117,8 @@ type AccountManager interface {
|
|||||||
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
|
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
|
||||||
GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error)
|
GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error)
|
||||||
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
|
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
|
||||||
LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
|
LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
|
||||||
SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
|
SyncPeer(sync PeerSync, account *Account) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
|
||||||
GetAllConnectedPeers() (map[string]struct{}, error)
|
GetAllConnectedPeers() (map[string]struct{}, error)
|
||||||
HasConnectedChannel(peerID string) bool
|
HasConnectedChannel(peerID string) bool
|
||||||
GetExternalCacheManager() ExternalCacheManager
|
GetExternalCacheManager() ExternalCacheManager
|
||||||
@@ -128,6 +130,8 @@ type AccountManager interface {
|
|||||||
UpdateIntegratedValidatorGroups(accountID string, userID string, groups []string) error
|
UpdateIntegratedValidatorGroups(accountID string, userID string, groups []string) error
|
||||||
GroupValidation(accountId string, groups []string) (bool, error)
|
GroupValidation(accountId string, groups []string) (bool, error)
|
||||||
GetValidatedPeers(account *Account) (map[string]struct{}, error)
|
GetValidatedPeers(account *Account) (map[string]struct{}, error)
|
||||||
|
SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *NetworkMap, error)
|
||||||
|
CancelPeerRoutines(peer *nbpeer.Peer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
@@ -227,7 +231,7 @@ type Account struct {
|
|||||||
Groups map[string]*nbgroup.Group `gorm:"-"`
|
Groups map[string]*nbgroup.Group `gorm:"-"`
|
||||||
GroupsG []nbgroup.Group `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
GroupsG []nbgroup.Group `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Policies []*Policy `gorm:"foreignKey:AccountID;references:id"`
|
Policies []*Policy `gorm:"foreignKey:AccountID;references:id"`
|
||||||
Routes map[string]*route.Route `gorm:"-"`
|
Routes map[route.ID]*route.Route `gorm:"-"`
|
||||||
RoutesG []route.Route `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
RoutesG []route.Route `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
NameServerGroups map[string]*nbdns.NameServerGroup `gorm:"-"`
|
NameServerGroups map[string]*nbdns.NameServerGroup `gorm:"-"`
|
||||||
NameServerGroupsG []nbdns.NameServerGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
NameServerGroupsG []nbdns.NameServerGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
@@ -264,7 +268,7 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*rou
|
|||||||
routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID)
|
routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID)
|
||||||
peerRoutesMembership := make(lookupMap)
|
peerRoutesMembership := make(lookupMap)
|
||||||
for _, r := range append(routes, peerDisabledRoutes...) {
|
for _, r := range append(routes, peerDisabledRoutes...) {
|
||||||
peerRoutesMembership[route.GetHAUniqueID(r)] = struct{}{}
|
peerRoutesMembership[string(route.GetHAUniqueID(r))] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
groupListMap := a.getPeerGroups(peerID)
|
groupListMap := a.getPeerGroups(peerID)
|
||||||
@@ -282,7 +286,7 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*rou
|
|||||||
func (a *Account) filterRoutesFromPeersOfSameHAGroup(routes []*route.Route, peerMemberships lookupMap) []*route.Route {
|
func (a *Account) filterRoutesFromPeersOfSameHAGroup(routes []*route.Route, peerMemberships lookupMap) []*route.Route {
|
||||||
var filteredRoutes []*route.Route
|
var filteredRoutes []*route.Route
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
_, found := peerMemberships[route.GetHAUniqueID(r)]
|
_, found := peerMemberships[string(route.GetHAUniqueID(r))]
|
||||||
if !found {
|
if !found {
|
||||||
filteredRoutes = append(filteredRoutes, r)
|
filteredRoutes = append(filteredRoutes, r)
|
||||||
}
|
}
|
||||||
@@ -321,7 +325,7 @@ func (a *Account) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Ro
|
|||||||
return enabledRoutes, disabledRoutes
|
return enabledRoutes, disabledRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
seenRoute := make(map[string]struct{})
|
seenRoute := make(map[route.ID]struct{})
|
||||||
|
|
||||||
takeRoute := func(r *route.Route, id string) {
|
takeRoute := func(r *route.Route, id string) {
|
||||||
if _, ok := seenRoute[r.ID]; ok {
|
if _, ok := seenRoute[r.ID]; ok {
|
||||||
@@ -352,7 +356,7 @@ func (a *Account) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Ro
|
|||||||
newPeerRoute := r.Copy()
|
newPeerRoute := r.Copy()
|
||||||
newPeerRoute.Peer = id
|
newPeerRoute.Peer = id
|
||||||
newPeerRoute.PeerGroups = nil
|
newPeerRoute.PeerGroups = nil
|
||||||
newPeerRoute.ID = r.ID + ":" + id // we have to provide unique route id when distribute network map
|
newPeerRoute.ID = route.ID(string(r.ID) + ":" + id) // we have to provide unique route id when distribute network map
|
||||||
takeRoute(newPeerRoute, id)
|
takeRoute(newPeerRoute, id)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -691,7 +695,7 @@ func (a *Account) Copy() *Account {
|
|||||||
policies = append(policies, policy.Copy())
|
policies = append(policies, policy.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
routes := map[string]*route.Route{}
|
routes := map[route.ID]*route.Route{}
|
||||||
for id, r := range a.Routes {
|
for id, r := range a.Routes {
|
||||||
routes[id] = r.Copy()
|
routes[id] = r.Copy()
|
||||||
}
|
}
|
||||||
@@ -956,7 +960,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
|
|||||||
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be smaller than one hour")
|
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be smaller than one hour")
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
@@ -1007,7 +1011,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
|
|||||||
|
|
||||||
func (am *DefaultAccountManager) peerLoginExpirationJob(accountID string) func() (time.Duration, bool) {
|
func (am *DefaultAccountManager) peerLoginExpirationJob(accountID string) func() (time.Duration, bool) {
|
||||||
return func() (time.Duration, bool) {
|
return func() (time.Duration, bool) {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
@@ -1092,19 +1096,21 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
|
|||||||
}
|
}
|
||||||
delete(userData, idp.UnsetAccountID)
|
delete(userData, idp.UnsetAccountID)
|
||||||
|
|
||||||
|
rcvdUsers := 0
|
||||||
for accountID, users := range userData {
|
for accountID, users := range userData {
|
||||||
|
rcvdUsers += len(users)
|
||||||
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Infof("warmed up IDP cache with %d entries", len(userData))
|
log.Infof("warmed up IDP cache with %d entries for %d accounts", rcvdUsers, len(userData))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAccount deletes an account and all its users from local store and from the remote IDP if the requester is an admin and account owner
|
// DeleteAccount deletes an account and all its users from local store and from the remote IDP if the requester is an admin and account owner
|
||||||
func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
|
func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1263,7 +1269,7 @@ func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountI
|
|||||||
|
|
||||||
// lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil
|
// lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil
|
||||||
func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Account) (*idp.UserData, error) {
|
func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Account) (*idp.UserData, error) {
|
||||||
users := make(map[string]struct{}, len(account.Users))
|
users := make(map[string]userLoggedInOnce, len(account.Users))
|
||||||
// ignore service users and users provisioned by integrations than are never logged in
|
// ignore service users and users provisioned by integrations than are never logged in
|
||||||
for _, user := range account.Users {
|
for _, user := range account.Users {
|
||||||
if user.IsServiceUser {
|
if user.IsServiceUser {
|
||||||
@@ -1272,7 +1278,7 @@ func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Accou
|
|||||||
if user.Issued == UserIssuedIntegration {
|
if user.Issued == UserIssuedIntegration {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
users[user.Id] = struct{}{}
|
users[user.Id] = userLoggedInOnce(!user.LastLogin.IsZero())
|
||||||
}
|
}
|
||||||
log.Debugf("looking up user %s of account %s in cache", userID, account.Id)
|
log.Debugf("looking up user %s of account %s in cache", userID, account.Id)
|
||||||
userData, err := am.lookupCache(users, account.Id)
|
userData, err := am.lookupCache(users, account.Id)
|
||||||
@@ -1345,22 +1351,57 @@ func (am *DefaultAccountManager) getAccountFromCache(accountID string, forceRelo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) lookupCache(accountUsers map[string]struct{}, accountID string) ([]*idp.UserData, error) {
|
func (am *DefaultAccountManager) lookupCache(accountUsers map[string]userLoggedInOnce, accountID string) ([]*idp.UserData, error) {
|
||||||
data, err := am.getAccountFromCache(accountID, false)
|
var data []*idp.UserData
|
||||||
|
var err error
|
||||||
|
|
||||||
|
maxAttempts := 2
|
||||||
|
|
||||||
|
data, err = am.getAccountFromCache(accountID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userDataMap := make(map[string]struct{})
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
|
if am.isCacheFresh(accountUsers, data) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt > 1 {
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("refreshing cache for account %s", accountID)
|
||||||
|
data, err = am.refreshCache(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt == maxAttempts {
|
||||||
|
log.Warnf("cache for account %s reached maximum refresh attempts (%d)", accountID, maxAttempts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCacheFresh checks if the cache is refreshed already by comparing the accountUsers with the cache data by user count and user invite status
|
||||||
|
func (am *DefaultAccountManager) isCacheFresh(accountUsers map[string]userLoggedInOnce, data []*idp.UserData) bool {
|
||||||
|
userDataMap := make(map[string]*idp.UserData, len(data))
|
||||||
for _, datum := range data {
|
for _, datum := range data {
|
||||||
userDataMap[datum.ID] = struct{}{}
|
userDataMap[datum.ID] = datum
|
||||||
}
|
}
|
||||||
|
|
||||||
// the accountUsers ID list of non integration users from store, we check if cache has all of them
|
// the accountUsers ID list of non integration users from store, we check if cache has all of them
|
||||||
// as result of for loop knownUsersCount will have number of users are not presented in the cashed
|
// as result of for loop knownUsersCount will have number of users are not presented in the cashed
|
||||||
knownUsersCount := len(accountUsers)
|
knownUsersCount := len(accountUsers)
|
||||||
for user := range accountUsers {
|
for user, loggedInOnce := range accountUsers {
|
||||||
if _, ok := userDataMap[user]; ok {
|
if datum, ok := userDataMap[user]; ok {
|
||||||
|
// check if the matching user data has a pending invite and if the user has logged in once, forcing the cache to be refreshed
|
||||||
|
if datum.AppMetadata.WTPendingInvite != nil && *datum.AppMetadata.WTPendingInvite && loggedInOnce == true { //nolint:gosimple
|
||||||
|
log.Infof("user %s has a pending invite and has logged in once, cache invalid", user)
|
||||||
|
return false
|
||||||
|
}
|
||||||
knownUsersCount--
|
knownUsersCount--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1369,15 +1410,11 @@ func (am *DefaultAccountManager) lookupCache(accountUsers map[string]struct{}, a
|
|||||||
|
|
||||||
// if we know users that are not yet in cache more likely cache is outdated
|
// if we know users that are not yet in cache more likely cache is outdated
|
||||||
if knownUsersCount > 0 {
|
if knownUsersCount > 0 {
|
||||||
log.Debugf("cache doesn't know about %d users from store, reloading", knownUsersCount)
|
log.Infof("cache invalid. Users unknown to the cache: %d", knownUsersCount)
|
||||||
// reload cache once avoiding loops
|
return false
|
||||||
data, err = am.refreshCache(accountID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, err
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) removeUserFromCache(accountID, userID string) error {
|
func (am *DefaultAccountManager) removeUserFromCache(accountID, userID string) error {
|
||||||
@@ -1425,29 +1462,14 @@ func (am *DefaultAccountManager) updateAccountDomainAttributes(account *Account,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
||||||
//
|
|
||||||
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
|
||||||
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
|
||||||
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
|
||||||
// was previously unclassified or classified as public so N users that logged int that time, has they own account
|
|
||||||
// and peers that shouldn't be lost.
|
|
||||||
func (am *DefaultAccountManager) handleExistingUserAccount(
|
func (am *DefaultAccountManager) handleExistingUserAccount(
|
||||||
existingAcc *Account,
|
existingAcc *Account,
|
||||||
domainAcc *Account,
|
primaryDomain bool,
|
||||||
claims jwtclaims.AuthorizationClaims,
|
claims jwtclaims.AuthorizationClaims,
|
||||||
) error {
|
) error {
|
||||||
var err error
|
err := am.updateAccountDomainAttributes(existingAcc, claims, primaryDomain)
|
||||||
|
if err != nil {
|
||||||
if domainAcc != nil && existingAcc.Id != domainAcc.Id {
|
return err
|
||||||
err = am.updateAccountDomainAttributes(existingAcc, claims, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = am.updateAccountDomainAttributes(existingAcc, claims, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should register the account ID to this user's metadata in our IDP manager
|
// we should register the account ID to this user's metadata in our IDP manager
|
||||||
@@ -1547,7 +1569,7 @@ func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock := am.Store.AcquireAccountLock(account.Id)
|
unlock := am.Store.AcquireAccountWriteLock(account.Id)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err = am.Store.GetAccountByUser(user.Id)
|
account, err = am.Store.GetAccountByUser(user.Id)
|
||||||
@@ -1630,7 +1652,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
unlock := am.Store.AcquireAccountLock(newAcc.Id)
|
unlock := am.Store.AcquireAccountWriteLock(newAcc.Id)
|
||||||
alreadyUnlocked := false
|
alreadyUnlocked := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !alreadyUnlocked {
|
if !alreadyUnlocked {
|
||||||
@@ -1649,7 +1671,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
|
|||||||
return nil, nil, status.Errorf(status.NotFound, "user %s not found", claims.UserId)
|
return nil, nil, status.Errorf(status.NotFound, "user %s not found", claims.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user.IsServiceUser {
|
if !user.IsServiceUser && claims.Invited {
|
||||||
err = am.redeemInvite(account, claims.UserId)
|
err = am.redeemInvite(account, claims.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -1781,12 +1803,33 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtcla
|
|||||||
|
|
||||||
account, err := am.Store.GetAccountByUser(claims.UserId)
|
account, err := am.Store.GetAccountByUser(claims.UserId)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = am.handleExistingUserAccount(account, domainAccount, claims)
|
unlockAccount := am.Store.AcquireAccountWriteLock(account.Id)
|
||||||
|
defer unlockAccount()
|
||||||
|
account, err = am.Store.GetAccountByUser(claims.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
||||||
|
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
||||||
|
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
||||||
|
// was previously unclassified or classified as public so N users that logged int that time, has they own account
|
||||||
|
// and peers that shouldn't be lost.
|
||||||
|
primaryDomain := domainAccount == nil || account.Id == domainAccount.Id
|
||||||
|
|
||||||
|
err = am.handleExistingUserAccount(account, primaryDomain, claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return account, nil
|
return account, nil
|
||||||
} else if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
} else if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
||||||
|
if domainAccount != nil {
|
||||||
|
unlockAccount := am.Store.AcquireAccountWriteLock(domainAccount.Id)
|
||||||
|
defer unlockAccount()
|
||||||
|
domainAccount, err = am.Store.GetAccountByPrivateDomain(claims.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return am.handleNewUserAccount(domainAccount, claims)
|
return am.handleNewUserAccount(domainAccount, claims)
|
||||||
} else {
|
} else {
|
||||||
// other error
|
// other error
|
||||||
@@ -1794,6 +1837,56 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtcla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *NetworkMap, error) {
|
||||||
|
accountID, err := am.Store.GetAccountIDByPeerPubKey(peerPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountReadLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, netMap, err := am.SyncPeer(PeerSync{WireGuardPubKey: peerPubKey}, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, mapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.MarkPeerConnected(peerPubKey, true, realIP, account)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed marking peer as connected %s %v", peerPubKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, netMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) CancelPeerRoutines(peer *nbpeer.Peer) error {
|
||||||
|
accountID, err := am.Store.GetAccountIDByPeerPubKey(peer.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.MarkPeerConnected(peer.Key, false, nil, account)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed marking peer as connected %s %v", peer.Key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// GetAllConnectedPeers returns connected peers based on peersUpdateManager.GetAllConnectedPeers()
|
// GetAllConnectedPeers returns connected peers based on peersUpdateManager.GetAllConnectedPeers()
|
||||||
func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, error) {
|
func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, error) {
|
||||||
return am.peersUpdateManager.GetAllConnectedPeers(), nil
|
return am.peersUpdateManager.GetAllConnectedPeers(), nil
|
||||||
@@ -1905,7 +1998,7 @@ func newAccountWithId(accountID, userID, domain string) *Account {
|
|||||||
network := NewNetwork()
|
network := NewNetwork()
|
||||||
peers := make(map[string]*nbpeer.Peer)
|
peers := make(map[string]*nbpeer.Peer)
|
||||||
users := make(map[string]*User)
|
users := make(map[string]*User)
|
||||||
routes := make(map[string]*route.Route)
|
routes := make(map[route.ID]*route.Route)
|
||||||
setupKeys := map[string]*SetupKey{}
|
setupKeys := map[string]*SetupKey{}
|
||||||
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
||||||
users[userID] = NewOwnerUser(userID)
|
users[userID] = NewOwnerUser(userID)
|
||||||
|
|||||||
@@ -1294,6 +1294,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := "account_creator"
|
userID := "account_creator"
|
||||||
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1408,7 +1409,7 @@ func TestFileStore_GetRoutesByPrefix(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
account := &Account{
|
account := &Account{
|
||||||
Routes: map[string]*route.Route{
|
Routes: map[route.ID]*route.Route{
|
||||||
"route-1": {
|
"route-1": {
|
||||||
ID: "route-1",
|
ID: "route-1",
|
||||||
Network: prefix,
|
Network: prefix,
|
||||||
@@ -1437,12 +1438,12 @@ func TestFileStore_GetRoutesByPrefix(t *testing.T) {
|
|||||||
routes := account.GetRoutesByPrefix(prefix)
|
routes := account.GetRoutesByPrefix(prefix)
|
||||||
|
|
||||||
assert.Len(t, routes, 2)
|
assert.Len(t, routes, 2)
|
||||||
routeIDs := make(map[string]struct{}, 2)
|
routeIDs := make(map[route.ID]struct{}, 2)
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
routeIDs[r.ID] = struct{}{}
|
routeIDs[r.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
assert.Contains(t, routeIDs, "route-1")
|
assert.Contains(t, routeIDs, route.ID("route-1"))
|
||||||
assert.Contains(t, routeIDs, "route-2")
|
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccount_GetRoutesToSync(t *testing.T) {
|
func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||||
@@ -1459,7 +1460,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
"peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
"peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
||||||
},
|
},
|
||||||
Groups: map[string]*group.Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
Groups: map[string]*group.Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
||||||
Routes: map[string]*route.Route{
|
Routes: map[route.ID]*route.Route{
|
||||||
"route-1": {
|
"route-1": {
|
||||||
ID: "route-1",
|
ID: "route-1",
|
||||||
Network: prefix,
|
Network: prefix,
|
||||||
@@ -1502,12 +1503,12 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
||||||
|
|
||||||
assert.Len(t, routes, 2)
|
assert.Len(t, routes, 2)
|
||||||
routeIDs := make(map[string]struct{}, 2)
|
routeIDs := make(map[route.ID]struct{}, 2)
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
routeIDs[r.ID] = struct{}{}
|
routeIDs[r.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
assert.Contains(t, routeIDs, "route-2")
|
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||||
assert.Contains(t, routeIDs, "route-3")
|
assert.Contains(t, routeIDs, route.ID("route-3"))
|
||||||
|
|
||||||
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
||||||
|
|
||||||
@@ -1573,7 +1574,7 @@ func TestAccount_Copy(t *testing.T) {
|
|||||||
SourcePostureChecks: make([]string, 0),
|
SourcePostureChecks: make([]string, 0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Routes: map[string]*route.Route{
|
Routes: map[route.ID]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
PeerGroups: []string{},
|
PeerGroups: []string{},
|
||||||
@@ -1655,7 +1656,8 @@ func TestDefaultAccountManager_DefaultAccountSettings(t *testing.T) {
|
|||||||
func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
require.NoError(t, err, "unable to create account manager")
|
require.NoError(t, err, "unable to create account manager")
|
||||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
|
||||||
|
_, err = manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
require.NoError(t, err, "unable to create an account")
|
require.NoError(t, err, "unable to create an account")
|
||||||
|
|
||||||
key, err := wgtypes.GenerateKey()
|
key, err := wgtypes.GenerateKey()
|
||||||
@@ -1666,7 +1668,10 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
|||||||
LoginExpirationEnabled: true,
|
LoginExpirationEnabled: true,
|
||||||
})
|
})
|
||||||
require.NoError(t, err, "unable to add peer")
|
require.NoError(t, err, "unable to add peer")
|
||||||
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil)
|
|
||||||
|
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
|
require.NoError(t, err, "unable to get the account")
|
||||||
|
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil, account)
|
||||||
require.NoError(t, err, "unable to mark peer connected")
|
require.NoError(t, err, "unable to mark peer connected")
|
||||||
account, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
account, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||||
PeerLoginExpiration: time.Hour,
|
PeerLoginExpiration: time.Hour,
|
||||||
@@ -1704,6 +1709,7 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
|||||||
func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.T) {
|
func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
require.NoError(t, err, "unable to create account manager")
|
require.NoError(t, err, "unable to create account manager")
|
||||||
|
|
||||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
require.NoError(t, err, "unable to create an account")
|
require.NoError(t, err, "unable to create an account")
|
||||||
|
|
||||||
@@ -1732,8 +1738,10 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account, err = manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
|
require.NoError(t, err, "unable to get the account")
|
||||||
// when we mark peer as connected, the peer login expiration routine should trigger
|
// when we mark peer as connected, the peer login expiration routine should trigger
|
||||||
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil)
|
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil, account)
|
||||||
require.NoError(t, err, "unable to mark peer connected")
|
require.NoError(t, err, "unable to mark peer connected")
|
||||||
|
|
||||||
failed := waitTimeout(wg, time.Second)
|
failed := waitTimeout(wg, time.Second)
|
||||||
@@ -1745,7 +1753,8 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
|
|||||||
func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *testing.T) {
|
func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
require.NoError(t, err, "unable to create account manager")
|
require.NoError(t, err, "unable to create account manager")
|
||||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
|
||||||
|
_, err = manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
require.NoError(t, err, "unable to create an account")
|
require.NoError(t, err, "unable to create an account")
|
||||||
|
|
||||||
key, err := wgtypes.GenerateKey()
|
key, err := wgtypes.GenerateKey()
|
||||||
@@ -1756,7 +1765,10 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
|
|||||||
LoginExpirationEnabled: true,
|
LoginExpirationEnabled: true,
|
||||||
})
|
})
|
||||||
require.NoError(t, err, "unable to add peer")
|
require.NoError(t, err, "unable to add peer")
|
||||||
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil)
|
|
||||||
|
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
|
require.NoError(t, err, "unable to get the account")
|
||||||
|
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil, account)
|
||||||
require.NoError(t, err, "unable to mark peer connected")
|
require.NoError(t, err, "unable to mark peer connected")
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
@@ -2259,21 +2271,29 @@ func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
|
|||||||
|
|
||||||
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
store, err := createStore(t)
|
store, err := createStore(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{})
|
|
||||||
|
manager, err := BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStore(t *testing.T) (Store, error) {
|
func createStore(t *testing.T) (Store, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
dataDir := t.TempDir()
|
dataDir := t.TempDir()
|
||||||
store, err := NewStoreFromJson(dataDir, nil)
|
store, cleanUp, err := NewTestStoreFromJson(dataDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
t.Cleanup(cleanUp)
|
||||||
|
|
||||||
return store, nil
|
return store, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (d DNSSettings) Copy() DNSSettings {
|
|||||||
|
|
||||||
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
|
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
|
||||||
func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) {
|
func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
@@ -57,7 +57,7 @@ func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string)
|
|||||||
|
|
||||||
// SaveDNSSettings validates a user role and updates the account's DNS settings
|
// SaveDNSSettings validates a user role and updates the account's DNS settings
|
||||||
func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
|
func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user