mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 15:36:51 -04:00
Compare commits
22 Commits
v0.25.5
...
debug-goog
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f41e2bd13 | ||
|
|
cb3408a10b | ||
|
|
0afd738509 | ||
|
|
e3d038da8a | ||
|
|
cf87f1e702 | ||
|
|
e890fdae54 | ||
|
|
dd14db6478 | ||
|
|
88747e3e01 | ||
|
|
fb30931365 | ||
|
|
a7547b9990 | ||
|
|
62bacee8dc | ||
|
|
71cd2e3e03 | ||
|
|
bdf71ab7ff | ||
|
|
a2f2a6e21a | ||
|
|
f89332fcd2 | ||
|
|
8604add997 | ||
|
|
93cab49696 | ||
|
|
b6835d9467 | ||
|
|
846d486366 | ||
|
|
9c56f74235 | ||
|
|
25b3641be8 | ||
|
|
c41504b571 |
18
.github/ISSUE_TEMPLATE/bug-issue-report.md
vendored
18
.github/ISSUE_TEMPLATE/bug-issue-report.md
vendored
@@ -2,15 +2,17 @@
|
||||
name: Bug/Issue report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: ['triage']
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the problem**
|
||||
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
@@ -18,13 +20,25 @@ Steps to reproduce the behavior:
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Are you using NetBird Cloud?**
|
||||
|
||||
Please specify whether you use NetBird Cloud or self-host NetBird's control plane.
|
||||
|
||||
**NetBird version**
|
||||
|
||||
`netbird version`
|
||||
|
||||
**NetBird status -d output:**
|
||||
If applicable, add the output of the `netbird status -d` command
|
||||
|
||||
If applicable, add the `netbird status -d' command output.
|
||||
|
||||
**Screenshots**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: ['feature-request']
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
@@ -139,6 +139,11 @@ func (c *Client) SetTraceLogLevel() {
|
||||
log.SetLevel(log.TraceLevel)
|
||||
}
|
||||
|
||||
// SetInfoLogLevel configure the logger to info level
|
||||
func (c *Client) SetInfoLogLevel() {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
}
|
||||
|
||||
// PeersList return with the list of the PeerInfos
|
||||
func (c *Client) PeersList() *PeerInfoArray {
|
||||
|
||||
|
||||
@@ -11,11 +11,12 @@ import (
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/server"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func (p *program) Start(svc service.Service) error {
|
||||
@@ -109,7 +110,6 @@ var runCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Netbird service is running")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -93,6 +94,12 @@ func runClient(
|
||||
) error {
|
||||
log.Infof("starting NetBird client version %s", version.NetbirdVersion())
|
||||
|
||||
// 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.
|
||||
if err := dns.CheckUncleanShutdown(config.WgIface); err != nil {
|
||||
log.Errorf("checking unclean shutdown error: %s", err)
|
||||
}
|
||||
|
||||
backOff := &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: 1,
|
||||
@@ -244,7 +251,7 @@ func runClient(
|
||||
|
||||
log.Info("stopped NetBird client")
|
||||
|
||||
if _, err := state.Status(); err == ErrResetConnection {
|
||||
if _, err := state.Status(); errors.Is(err, ErrResetConnection) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
const dbusDefaultFlag = 0
|
||||
@@ -14,6 +16,7 @@ const dbusDefaultFlag = 0
|
||||
func isDbusListenerRunning(dest string, path dbus.ObjectPath) bool {
|
||||
obj, closeConn, err := getDbusObject(dest, path)
|
||||
if err != nil {
|
||||
log.Tracef("error getting dbus object: %s", err)
|
||||
return false
|
||||
}
|
||||
defer closeConn()
|
||||
@@ -21,14 +24,18 @@ func isDbusListenerRunning(dest string, path dbus.ObjectPath) bool {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store()
|
||||
return err == nil
|
||||
if err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store(); err != nil {
|
||||
log.Tracef("error calling dbus: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getDbusObject(dest string, path dbus.ObjectPath) (dbus.BusObject, func(), error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, fmt.Errorf("get dbus: %w", err)
|
||||
}
|
||||
obj := conn.Object(dest, path)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package dns
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -49,7 +50,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
if backupFileExist {
|
||||
err = f.restore()
|
||||
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: %s", err)
|
||||
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("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
|
||||
@@ -58,7 +59,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
if !backupFileExist {
|
||||
err = f.backup()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to backup the resolv.conf file")
|
||||
return fmt.Errorf("unable to backup the resolv.conf file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
|
||||
resolvConf, err := parseBackupResolvConf()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.Errorf("could not read original search domains from %s: %s", fileDefaultResolvConfBackupLocation, err)
|
||||
}
|
||||
|
||||
f.repair.stopWatchFileChanges()
|
||||
@@ -96,10 +97,16 @@ func (f *fileConfigurator) updateConfig(nbSearchDomains []string, nbNameserverIP
|
||||
if restoreErr != nil {
|
||||
log.Errorf("attempt to restore default file failed with error: %s", err)
|
||||
}
|
||||
return fmt.Errorf("got an creating resolver file %s. Error: %s", defaultResolvConfPath, err)
|
||||
return fmt.Errorf("creating resolver file %s. Error: %w", defaultResolvConfPath, err)
|
||||
}
|
||||
|
||||
log.Infof("created a NetBird managed %s file with the DNS settings. Added %d search domains. Search list: %s", defaultResolvConfPath, len(searchDomainList), searchDomainList)
|
||||
|
||||
// create another backup for unclean shutdown detection right after overwriting the original resolv.conf
|
||||
if err := createUncleanShutdownIndicator(fileDefaultResolvConfBackupLocation, fileManager, nbNameserverIP); err != nil {
|
||||
log.Errorf("failed to create unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
log.Infof("created a NetBird managed %s file with your DNS settings. Added %d search domains. Search list: %s", defaultResolvConfPath, len(searchDomainList), searchDomainList)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -111,14 +118,14 @@ func (f *fileConfigurator) restoreHostDNS() error {
|
||||
func (f *fileConfigurator) backup() error {
|
||||
stats, err := os.Stat(defaultResolvConfPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while checking stats for %s file. Error: %s", defaultResolvConfPath, err)
|
||||
return fmt.Errorf("checking stats for %s file. Error: %w", defaultResolvConfPath, err)
|
||||
}
|
||||
|
||||
f.originalPerms = stats.Mode()
|
||||
|
||||
err = copyFile(defaultResolvConfPath, fileDefaultResolvConfBackupLocation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while backing up the %s file. Error: %s", defaultResolvConfPath, err)
|
||||
return fmt.Errorf("backing up %s: %w", defaultResolvConfPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -126,12 +133,58 @@ func (f *fileConfigurator) backup() error {
|
||||
func (f *fileConfigurator) restore() error {
|
||||
err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while restoring the %s file from %s. Error: %s", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
||||
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
return os.RemoveAll(fileDefaultResolvConfBackupLocation)
|
||||
}
|
||||
|
||||
func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Addr) error {
|
||||
resolvConf, err := parseDefaultResolvConf()
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse current resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
// no current nameservers set -> restore
|
||||
if len(resolvConf.nameServers) == 0 {
|
||||
return restoreResolvConfFile()
|
||||
}
|
||||
|
||||
currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0])
|
||||
// not a valid first nameserver -> restore
|
||||
if err != nil {
|
||||
log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[1], err)
|
||||
return restoreResolvConfFile()
|
||||
}
|
||||
|
||||
// current address is still netbird's non-available dns address -> restore
|
||||
// comparing parsed addresses only, to remove ambiguity
|
||||
if currentDNSAddress.String() == storedDNSAddress.String() {
|
||||
return restoreResolvConfFile()
|
||||
}
|
||||
|
||||
log.Info("restoring unclean shutdown: first current nameserver differs from saved nameserver pre-netbird: not restoring")
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreResolvConfFile() error {
|
||||
log.Debugf("restoring unclean shutdown: restoring %s from %s", defaultResolvConfPath, fileUncleanShutdownResolvConfLocation)
|
||||
|
||||
if err := copyFile(fileUncleanShutdownResolvConfLocation, defaultResolvConfPath); err != nil {
|
||||
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileUncleanShutdownResolvConfLocation, err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown resolv.conf file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateNsList generates a list of nameservers from the config and adds the primary nameserver to the beginning of the list
|
||||
func generateNsList(nbNameserverIP string, cfg *resolvConf) []string {
|
||||
ns := make([]string, 1, len(cfg.nameServers)+1)
|
||||
@@ -231,17 +284,17 @@ func validateAndFillSearchDomains(initialLineChars int, s *[]string, vs []string
|
||||
func copyFile(src, dest string) error {
|
||||
stats, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while checking stats for %s file when copying it. Error: %s", src, err)
|
||||
return fmt.Errorf("checking stats for %s file when copying it. Error: %s", src, err)
|
||||
}
|
||||
|
||||
bytesRead, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while reading the file %s file for copy. Error: %s", src, err)
|
||||
return fmt.Errorf("reading the file %s file for copy. Error: %s", src, err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(dest, bytesRead, stats.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an writing the destination file %s for copy. Error: %s", dest, err)
|
||||
return fmt.Errorf("writing the destination file %s for copy. Error: %s", dest, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -33,9 +33,15 @@ func parseBackupResolvConf() (*resolvConf, error) {
|
||||
}
|
||||
|
||||
func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
||||
rconf := &resolvConf{
|
||||
searchDomains: make([]string, 0),
|
||||
nameServers: make([]string, 0),
|
||||
others: make([]string, 0),
|
||||
}
|
||||
|
||||
file, err := os.Open(resolvConfFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open %s file: %w", resolvConfFile, err)
|
||||
return rconf, fmt.Errorf("failed to open %s file: %w", resolvConfFile, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
@@ -45,17 +51,11 @@ func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
||||
|
||||
cur, err := os.ReadFile(resolvConfFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %s file: %w", resolvConfFile, err)
|
||||
return rconf, fmt.Errorf("failed to read %s file: %w", resolvConfFile, err)
|
||||
}
|
||||
|
||||
if len(cur) == 0 {
|
||||
return nil, fmt.Errorf("file is empty")
|
||||
}
|
||||
|
||||
rconf := &resolvConf{
|
||||
searchDomains: make([]string, 0),
|
||||
nameServers: make([]string, 0),
|
||||
others: make([]string, 0),
|
||||
return rconf, fmt.Errorf("file is empty")
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(cur), "\n") {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -16,11 +16,11 @@ func Test_parseResolvConf(t *testing.T) {
|
||||
expectedOther []string
|
||||
}{
|
||||
{
|
||||
input: `domain chello.hu
|
||||
search chello.hu
|
||||
input: `domain example.org
|
||||
search example.org
|
||||
nameserver 192.168.0.1
|
||||
`,
|
||||
expectedSearch: []string{"chello.hu"},
|
||||
expectedSearch: []string{"example.org"},
|
||||
expectedNS: []string{"192.168.0.1"},
|
||||
expectedOther: []string{},
|
||||
},
|
||||
@@ -75,40 +75,13 @@ options debug
|
||||
expectedNS: []string{"192.168.2.1", "100.81.99.197"},
|
||||
expectedOther: []string{"options debug"},
|
||||
},
|
||||
{
|
||||
input: `# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
|
||||
# Do not edit.
|
||||
#
|
||||
# This file might be symlinked as /etc/resolv.conf. If you're looking at
|
||||
# /etc/resolv.conf and seeing this text, you have followed the symlink.
|
||||
#
|
||||
# This is a dynamic resolv.conf file for connecting local clients directly to
|
||||
# all known uplink DNS servers. This file lists all configured search domains.
|
||||
#
|
||||
# Third party programs should typically not access this file directly, but only
|
||||
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
|
||||
# different way, replace this symlink by a static file or a different symlink.
|
||||
#
|
||||
# See man:systemd-resolved.service(8) for details about the supported modes of
|
||||
# operation for /etc/resolv.conf.
|
||||
|
||||
nameserver 192.168.2.1
|
||||
nameserver 100.81.99.197
|
||||
search netbird.cloud
|
||||
options debug
|
||||
options edns0 trust-ad
|
||||
`,
|
||||
expectedSearch: []string{"netbird.cloud"},
|
||||
expectedNS: []string{"192.168.2.1", "100.81.99.197"},
|
||||
expectedOther: []string{"options debug", "options edns0 trust-ad"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run("test", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpResolvConf := fmt.Sprintf("%s/%s", t.TempDir(), "resolv.conf")
|
||||
tmpResolvConf := filepath.Join(t.TempDir(), "resolv.conf")
|
||||
err := os.WriteFile(tmpResolvConf, []byte(testCase.input), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -147,3 +120,55 @@ func compareLists(search []string, search2 []string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Test_emptyFile(t *testing.T) {
|
||||
cfg, err := parseResolvConfFile("/tmp/nothing")
|
||||
if err == nil {
|
||||
t.Errorf("expected error, got nil")
|
||||
}
|
||||
if len(cfg.others) != 0 || len(cfg.searchDomains) != 0 || len(cfg.nameServers) != 0 {
|
||||
t.Errorf("expected empty config, got %v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_symlink(t *testing.T) {
|
||||
input := `# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
|
||||
# Do not edit.
|
||||
#
|
||||
# This file might be symlinked as /etc/resolv.conf. If you're looking at
|
||||
# /etc/resolv.conf and seeing this text, you have followed the symlink.
|
||||
#
|
||||
# This is a dynamic resolv.conf file for connecting local clients directly to
|
||||
# all known uplink DNS servers. This file lists all configured search domains.
|
||||
#
|
||||
# Third party programs should typically not access this file directly, but only
|
||||
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
|
||||
# different way, replace this symlink by a static file or a different symlink.
|
||||
#
|
||||
# See man:systemd-resolved.service(8) for details about the supported modes of
|
||||
# operation for /etc/resolv.conf.
|
||||
|
||||
nameserver 192.168.0.1
|
||||
`
|
||||
|
||||
tmpResolvConf := filepath.Join(t.TempDir(), "resolv.conf")
|
||||
err := os.WriteFile(tmpResolvConf, []byte(input), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpLink := filepath.Join(t.TempDir(), "symlink")
|
||||
err = os.Symlink(tmpResolvConf, tmpLink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err := parseResolvConfFile(tmpLink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(cfg.nameServers) != 1 {
|
||||
t.Errorf("unexpected resolv.conf content: %v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
@@ -32,9 +32,10 @@ type repair struct {
|
||||
}
|
||||
|
||||
func newRepair(operationFile string, updateFn repairConfFn) *repair {
|
||||
targetFile := targetFile(operationFile)
|
||||
return &repair{
|
||||
operationFile: operationFile,
|
||||
watchDir: path.Dir(operationFile),
|
||||
operationFile: targetFile,
|
||||
watchDir: path.Dir(targetFile),
|
||||
updateFn: updateFn,
|
||||
}
|
||||
}
|
||||
@@ -44,7 +45,7 @@ func (f *repair) watchFileChanges(nbSearchDomains []string, nbNameserverIP strin
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("start to watch resolv.conf")
|
||||
log.Infof("start to watch resolv.conf: %s", f.operationFile)
|
||||
inotify, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Errorf("failed to start inotify watcher for resolv.conf: %s", err)
|
||||
@@ -60,7 +61,7 @@ func (f *repair) watchFileChanges(nbSearchDomains []string, nbNameserverIP strin
|
||||
continue
|
||||
}
|
||||
|
||||
log.Tracef("resolv.conf changed, check if it is broken")
|
||||
log.Tracef("%s changed, check if it is broken", f.operationFile)
|
||||
|
||||
rConf, err := parseResolvConfFile(f.operationFile)
|
||||
if err != nil {
|
||||
@@ -87,7 +88,7 @@ func (f *repair) watchFileChanges(nbSearchDomains []string, nbNameserverIP strin
|
||||
|
||||
err = f.inotify.Add(f.watchDir)
|
||||
if err != nil {
|
||||
log.Errorf("failed to readd inotify watch for resolv.conf: %s", err)
|
||||
log.Errorf("failed to re-add inotify watch for resolv.conf: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -124,8 +125,7 @@ func (f *repair) isEventRelevant(event fsnotify.Event) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
operationFileSymlink := fmt.Sprintf("%s~", f.operationFile)
|
||||
if event.Name == f.operationFile || event.Name == operationFileSymlink {
|
||||
if event.Name == f.operationFile {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -149,3 +149,11 @@ func isNbParamsMissing(nbSearchDomains []string, nbNameserverIP string, rConf *r
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func targetFile(filename string) string {
|
||||
target, err := filepath.EvalSymlinks(filename)
|
||||
if err != nil {
|
||||
log.Errorf("evarl err: %s", err)
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -126,5 +127,49 @@ nameserver 8.8.8.8`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_newRepairSymlink(t *testing.T) {
|
||||
resolvConfContent := `
|
||||
nameserver 10.0.0.1
|
||||
nameserver 8.8.8.8
|
||||
searchdomain netbird.cloud something`
|
||||
|
||||
modifyContent := `nameserver 8.8.8.8`
|
||||
|
||||
tmpResolvConf := filepath.Join(t.TempDir(), "resolv.conf")
|
||||
err := os.WriteFile(tmpResolvConf, []byte(resolvConfContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpLink := filepath.Join(t.TempDir(), "symlink")
|
||||
err = os.Symlink(tmpResolvConf, tmpLink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var changed bool
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
updateFn := func([]string, string, *resolvConf) error {
|
||||
changed = true
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
r := newRepair(tmpLink, updateFn)
|
||||
r.watchFileChanges([]string{"netbird.cloud"}, "10.0.0.1")
|
||||
|
||||
err = os.WriteFile(tmpLink, []byte(modifyContent), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write out resolv.conf: %s", err)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
r.stopWatchFileChanges()
|
||||
|
||||
if changed != true {
|
||||
t.Errorf("unexpected result: want: %v, got: %v", true, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
@@ -11,6 +12,7 @@ type hostManager interface {
|
||||
applyDNSConfig(config HostDNSConfig) error
|
||||
restoreHostDNS() error
|
||||
supportCustomPort() bool
|
||||
restoreUncleanShutdownDNS(storedDNSAddress *netip.Addr) error
|
||||
}
|
||||
|
||||
type HostDNSConfig struct {
|
||||
@@ -27,9 +29,10 @@ type DomainConfig struct {
|
||||
}
|
||||
|
||||
type mockHostConfigurator struct {
|
||||
applyDNSConfigFunc func(config HostDNSConfig) error
|
||||
restoreHostDNSFunc func() error
|
||||
supportCustomPortFunc func() bool
|
||||
applyDNSConfigFunc func(config HostDNSConfig) error
|
||||
restoreHostDNSFunc func() error
|
||||
supportCustomPortFunc func() bool
|
||||
restoreUncleanShutdownDNSFunc func(*netip.Addr) error
|
||||
}
|
||||
|
||||
func (m *mockHostConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
@@ -53,11 +56,19 @@ func (m *mockHostConfigurator) supportCustomPort() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *mockHostConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Addr) error {
|
||||
if m.restoreUncleanShutdownDNSFunc != nil {
|
||||
return m.restoreUncleanShutdownDNSFunc(storedDNSAddress)
|
||||
}
|
||||
return fmt.Errorf("method restoreUncleanShutdownDNS is not implemented")
|
||||
}
|
||||
|
||||
func newNoopHostMocker() hostManager {
|
||||
return &mockHostConfigurator{
|
||||
applyDNSConfigFunc: func(config HostDNSConfig) error { return nil },
|
||||
restoreHostDNSFunc: func() error { return nil },
|
||||
supportCustomPortFunc: func() bool { return true },
|
||||
applyDNSConfigFunc: func(config HostDNSConfig) error { return nil },
|
||||
restoreHostDNSFunc: func() error { return nil },
|
||||
supportCustomPortFunc: func() bool { return true },
|
||||
restoreUncleanShutdownDNSFunc: func(*netip.Addr) error { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package dns
|
||||
|
||||
import "net/netip"
|
||||
|
||||
type androidHostManager struct {
|
||||
}
|
||||
|
||||
func newHostManager(wgInterface WGIface) (hostManager, error) {
|
||||
func newHostManager() (hostManager, error) {
|
||||
return &androidHostManager{}, nil
|
||||
}
|
||||
|
||||
@@ -18,3 +20,7 @@ func (a androidHostManager) restoreHostDNS() error {
|
||||
func (a androidHostManager) supportCustomPort() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a androidHostManager) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -34,7 +36,7 @@ type systemConfigurator struct {
|
||||
createdKeys map[string]struct{}
|
||||
}
|
||||
|
||||
func newHostManager(_ WGIface) (hostManager, error) {
|
||||
func newHostManager() (hostManager, error) {
|
||||
return &systemConfigurator{
|
||||
createdKeys: make(map[string]struct{}),
|
||||
}, nil
|
||||
@@ -50,17 +52,22 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
if config.RouteAll {
|
||||
err = s.addDNSSetupForAll(config.ServerIP, config.ServerPort)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add dns setup for all: %w", err)
|
||||
}
|
||||
} else if s.primaryServiceID != "" {
|
||||
err = s.removeKeyFromSystemConfig(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("remote key from system config: %w", err)
|
||||
}
|
||||
s.primaryServiceID = ""
|
||||
log.Infof("removed %s:%d as main DNS resolver for this peer", config.ServerIP, config.ServerPort)
|
||||
}
|
||||
|
||||
// create a file for unclean shutdown detection
|
||||
if err := createUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to create unclean shutdown file: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
searchDomains []string
|
||||
matchDomains []string
|
||||
@@ -85,7 +92,7 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
err = s.removeKeyFromSystemConfig(matchKey)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add match domains: %w", err)
|
||||
}
|
||||
|
||||
searchKey := getKeyWithInput(netbirdDNSStateKeyFormat, searchSuffix)
|
||||
@@ -96,7 +103,7 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
err = s.removeKeyFromSystemConfig(searchKey)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add search domains: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -119,7 +126,11 @@ func (s *systemConfigurator) restoreHostDNS() error {
|
||||
_, err := runSystemConfigCommand(wrapCommand(lines))
|
||||
if err != nil {
|
||||
log.Errorf("got an error while cleaning the system configuration: %s", err)
|
||||
return err
|
||||
return fmt.Errorf("clean system: %w", err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -129,7 +140,7 @@ func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
|
||||
line := buildRemoveKeyOperation(key)
|
||||
_, err := runSystemConfigCommand(wrapCommand(line))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("remove key: %w", err)
|
||||
}
|
||||
|
||||
delete(s.createdKeys, key)
|
||||
@@ -140,7 +151,7 @@ func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
|
||||
func (s *systemConfigurator) addSearchDomains(key, domains string, ip string, port int) error {
|
||||
err := s.addDNSState(key, domains, ip, port, true)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add dns state: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("added %d search domains to the state. Domain list: %s", len(strings.Split(domains, " ")), domains)
|
||||
@@ -153,7 +164,7 @@ func (s *systemConfigurator) addSearchDomains(key, domains string, ip string, po
|
||||
func (s *systemConfigurator) addMatchDomains(key, domains, dnsServer string, port int) error {
|
||||
err := s.addDNSState(key, domains, dnsServer, port, false)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add dns state: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("added %d match domains to the state. Domain list: %s", len(strings.Split(domains, " ")), domains)
|
||||
@@ -178,33 +189,37 @@ func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port
|
||||
|
||||
_, err := runSystemConfigCommand(stdinCommands)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while applying state for domains %s, error: %s", domains, err)
|
||||
return fmt.Errorf("applying state for domains %s, error: %w", domains, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error {
|
||||
primaryServiceKey, existingNameserver := s.getPrimaryService()
|
||||
if primaryServiceKey == "" {
|
||||
return fmt.Errorf("couldn't find the primary service key")
|
||||
primaryServiceKey, existingNameserver, err := s.getPrimaryService()
|
||||
if err != nil || primaryServiceKey == "" {
|
||||
return fmt.Errorf("couldn't find the primary service key: %w", err)
|
||||
}
|
||||
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port, existingNameserver)
|
||||
|
||||
err = s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port, existingNameserver)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add dns setup: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("configured %s:%d as main DNS resolver for this peer", dnsServer, port)
|
||||
s.primaryServiceID = primaryServiceKey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *systemConfigurator) getPrimaryService() (string, string) {
|
||||
func (s *systemConfigurator) getPrimaryService() (string, string, error) {
|
||||
line := buildCommandLine("show", globalIPv4State, "")
|
||||
stdinCommands := wrapCommand(line)
|
||||
|
||||
b, err := runSystemConfigCommand(stdinCommands)
|
||||
if err != nil {
|
||||
log.Error("got error while sending the command: ", err)
|
||||
return "", ""
|
||||
return "", "", fmt.Errorf("sending the command: %w", err)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(b))
|
||||
primaryService := ""
|
||||
router := ""
|
||||
@@ -217,7 +232,11 @@ func (s *systemConfigurator) getPrimaryService() (string, string) {
|
||||
router = strings.TrimSpace(strings.Split(text, ":")[1])
|
||||
}
|
||||
}
|
||||
return primaryService, router
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return primaryService, router, fmt.Errorf("scan: %w", err)
|
||||
}
|
||||
|
||||
return primaryService, router, nil
|
||||
}
|
||||
|
||||
func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int, existingDNSServer string) error {
|
||||
@@ -228,7 +247,14 @@ func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int, e
|
||||
stdinCommands := wrapCommand(addDomainCommand)
|
||||
_, err := runSystemConfigCommand(stdinCommands)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while applying dns setup, error: %s", err)
|
||||
return fmt.Errorf("applying dns setup, error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *systemConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
if err := s.restoreHostDNS(); err != nil {
|
||||
return fmt.Errorf("restoring dns via scutil: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -266,7 +292,7 @@ func runSystemConfigCommand(command string) ([]byte, error) {
|
||||
cmd.Stdin = strings.NewReader(command)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("got error while running system configuration command: \"%s\", error: %s", command, err)
|
||||
return nil, fmt.Errorf("running system configuration command: \"%s\", error: %w", command, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package dns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -20,7 +22,7 @@ func newHostManager(dnsManager IosDnsManager) (hostManager, error) {
|
||||
func (a iosHostManager) applyDNSConfig(config HostDNSConfig) error {
|
||||
jsonData, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("marshal: %w", err)
|
||||
}
|
||||
jsonString := string(jsonData)
|
||||
log.Debugf("Applying DNS settings: %s", jsonString)
|
||||
@@ -35,3 +37,7 @@ func (a iosHostManager) restoreHostDNS() error {
|
||||
func (a iosHostManager) supportCustomPort() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a iosHostManager) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -19,8 +21,27 @@ const (
|
||||
resolvConfManager
|
||||
)
|
||||
|
||||
var ErrUnknownOsManagerType = errors.New("unknown os manager type")
|
||||
|
||||
type osManagerType int
|
||||
|
||||
func newOsManagerType(osManager string) (osManagerType, error) {
|
||||
switch osManager {
|
||||
case "netbird":
|
||||
return fileManager, nil
|
||||
case "file":
|
||||
return netbirdManager, nil
|
||||
case "networkManager":
|
||||
return networkManager, nil
|
||||
case "systemd":
|
||||
return systemdManager, nil
|
||||
case "resolvconf":
|
||||
return resolvConfManager, nil
|
||||
default:
|
||||
return 0, ErrUnknownOsManagerType
|
||||
}
|
||||
}
|
||||
|
||||
func (t osManagerType) String() string {
|
||||
switch t {
|
||||
case netbirdManager:
|
||||
@@ -38,13 +59,17 @@ func (t osManagerType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func newHostManager(wgInterface WGIface) (hostManager, error) {
|
||||
func newHostManager(wgInterface string) (hostManager, error) {
|
||||
osManager, err := getOSDNSManagerType()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("discovered mode is: %s", osManager)
|
||||
return newHostManagerFromType(wgInterface, osManager)
|
||||
}
|
||||
|
||||
func newHostManagerFromType(wgInterface string, osManager osManagerType) (hostManager, error) {
|
||||
switch osManager {
|
||||
case networkManager:
|
||||
return newNetworkManagerDbusConfigurator(wgInterface)
|
||||
@@ -58,12 +83,15 @@ func newHostManager(wgInterface WGIface) (hostManager, error) {
|
||||
}
|
||||
|
||||
func getOSDNSManagerType() (osManagerType, error) {
|
||||
|
||||
file, err := os.Open(defaultResolvConfPath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to open %s for checking owner, got error: %s", defaultResolvConfPath, err)
|
||||
return 0, fmt.Errorf("unable to open %s for checking owner, got error: %w", defaultResolvConfPath, err)
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Errorf("close file %s: %s", defaultResolvConfPath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
@@ -101,6 +129,10 @@ func getOSDNSManagerType() (osManagerType, error) {
|
||||
return resolvConfManager, nil
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return 0, fmt.Errorf("scan: %w", err)
|
||||
}
|
||||
|
||||
return fileManager, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -9,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
dnsPolicyConfigMatchPath = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig\\NetBird-Match"
|
||||
dnsPolicyConfigMatchPath = `SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\NetBird-Match`
|
||||
dnsPolicyConfigVersionKey = "Version"
|
||||
dnsPolicyConfigVersionValue = 2
|
||||
dnsPolicyConfigNameKey = "Name"
|
||||
@@ -19,7 +21,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
interfaceConfigPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"
|
||||
interfaceConfigPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces`
|
||||
interfaceConfigNameServerKey = "NameServer"
|
||||
interfaceConfigSearchListKey = "SearchList"
|
||||
)
|
||||
@@ -34,12 +36,16 @@ func newHostManager(wgInterface WGIface) (hostManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newHostManagerWithGuid(guid)
|
||||
}
|
||||
|
||||
func newHostManagerWithGuid(guid string) (hostManager, error) {
|
||||
return ®istryConfigurator{
|
||||
guid: guid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *registryConfigurator) supportCustomPort() bool {
|
||||
func (r *registryConfigurator) supportCustomPort() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -48,17 +54,22 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
if config.RouteAll {
|
||||
err = r.addDNSSetupForAll(config.ServerIP)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add dns setup: %w", err)
|
||||
}
|
||||
} else if r.routingAll {
|
||||
err = r.deleteInterfaceRegistryKeyProperty(interfaceConfigNameServerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("delete interface registry key property: %w", err)
|
||||
}
|
||||
r.routingAll = false
|
||||
log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP)
|
||||
}
|
||||
|
||||
// create a file for unclean shutdown detection
|
||||
if err := createUncleanShutdownIndicator(r.guid); err != nil {
|
||||
log.Errorf("failed to create unclean shutdown file: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
searchDomains []string
|
||||
matchDomains []string
|
||||
@@ -80,12 +91,12 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
err = removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add dns match policy: %w", err)
|
||||
}
|
||||
|
||||
err = r.updateSearchDomains(searchDomains)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("update search domains: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -94,7 +105,7 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
|
||||
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding dns setup for all failed with error: %s", err)
|
||||
return fmt.Errorf("adding dns setup for all failed with error: %w", err)
|
||||
}
|
||||
r.routingAll = true
|
||||
log.Infof("configured %s:53 as main DNS forwarder for this peer", ip)
|
||||
@@ -106,33 +117,33 @@ func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) er
|
||||
if err == nil {
|
||||
err = registry.DeleteKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %s", dnsPolicyConfigMatchPath, err)
|
||||
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %w", dnsPolicyConfigMatchPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
regKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %s", dnsPolicyConfigMatchPath, err)
|
||||
return fmt.Errorf("unable to create registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %w", dnsPolicyConfigMatchPath, err)
|
||||
}
|
||||
|
||||
err = regKey.SetDWordValue(dnsPolicyConfigVersionKey, dnsPolicyConfigVersionValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigVersionKey, err)
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigVersionKey, err)
|
||||
}
|
||||
|
||||
err = regKey.SetStringsValue(dnsPolicyConfigNameKey, domains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigNameKey, err)
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigNameKey, err)
|
||||
}
|
||||
|
||||
err = regKey.SetStringValue(dnsPolicyConfigGenericDNSServersKey, ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigGenericDNSServersKey, err)
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigGenericDNSServersKey, err)
|
||||
}
|
||||
|
||||
err = regKey.SetDWordValue(dnsPolicyConfigConfigOptionsKey, dnsPolicyConfigConfigOptionsValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigConfigOptionsKey, err)
|
||||
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigConfigOptionsKey, err)
|
||||
}
|
||||
|
||||
log.Infof("added %d match domains to the state. Domain list: %s", len(domains), domains)
|
||||
@@ -141,18 +152,25 @@ func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) er
|
||||
}
|
||||
|
||||
func (r *registryConfigurator) restoreHostDNS() error {
|
||||
err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
|
||||
log.Errorf("remove registry key from dns policy config: %s", err)
|
||||
}
|
||||
|
||||
return r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey)
|
||||
if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey); err != nil {
|
||||
return fmt.Errorf("remove interface registry key: %w", err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *registryConfigurator) updateSearchDomains(domains []string) error {
|
||||
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ","))
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding search domain failed with error: %s", err)
|
||||
return fmt.Errorf("adding search domain failed with error: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("updated the search domains in the registry with %d domains. Domain list: %s", len(domains), domains)
|
||||
@@ -163,13 +181,13 @@ func (r *registryConfigurator) updateSearchDomains(domains []string) error {
|
||||
func (r *registryConfigurator) setInterfaceRegistryKeyStringValue(key, value string) error {
|
||||
regKey, err := r.getInterfaceRegistryKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("get interface registry key: %w", err)
|
||||
}
|
||||
defer regKey.Close()
|
||||
defer closer(regKey)
|
||||
|
||||
err = regKey.SetStringValue(key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("applying key %s with value \"%s\" for interface failed with error: %s", key, value, err)
|
||||
return fmt.Errorf("applying key %s with value \"%s\" for interface failed with error: %w", key, value, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -178,13 +196,13 @@ func (r *registryConfigurator) setInterfaceRegistryKeyStringValue(key, value str
|
||||
func (r *registryConfigurator) deleteInterfaceRegistryKeyProperty(propertyKey string) error {
|
||||
regKey, err := r.getInterfaceRegistryKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("get interface registry key: %w", err)
|
||||
}
|
||||
defer regKey.Close()
|
||||
defer closer(regKey)
|
||||
|
||||
err = regKey.DeleteValue(propertyKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting registry key %s for interface failed with error: %s", propertyKey, err)
|
||||
return fmt.Errorf("deleting registry key %s for interface failed with error: %w", propertyKey, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -197,20 +215,33 @@ func (r *registryConfigurator) getInterfaceRegistryKey() (registry.Key, error) {
|
||||
|
||||
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return regKey, fmt.Errorf("unable to open the interface registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %s", regKeyPath, err)
|
||||
return regKey, fmt.Errorf("unable to open the interface registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %w", regKeyPath, err)
|
||||
}
|
||||
|
||||
return regKey, nil
|
||||
}
|
||||
|
||||
func (r *registryConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
if err := r.restoreHostDNS(); err != nil {
|
||||
return fmt.Errorf("restoring dns via registry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeRegistryKeyFromDNSPolicyConfig(regKeyPath string) error {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
k.Close()
|
||||
defer closer(k)
|
||||
err = registry.DeleteKey(registry.LOCAL_MACHINE, regKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %s", regKeyPath, err)
|
||||
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %w", regKeyPath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func closer(closer io.Closer) {
|
||||
if err := closer.Close(); err != nil {
|
||||
log.Errorf("failed to close: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (d *localResolver) lookupRecord(r *dns.Msg) dns.RR {
|
||||
func (d *localResolver) registerRecord(record nbdns.SimpleRecord) error {
|
||||
fullRecord, err := dns.NewRR(record.String())
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("register record: %w", err)
|
||||
}
|
||||
|
||||
fullRecord.Header().Rdlength = record.Len()
|
||||
|
||||
@@ -5,8 +5,10 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
@@ -41,9 +43,13 @@ const (
|
||||
networkManagerDbusPrimaryDNSPriority int32 = -500
|
||||
networkManagerDbusWithMatchDomainPriority int32 = 0
|
||||
networkManagerDbusSearchDomainOnlyPriority int32 = 50
|
||||
supportedNetworkManagerVersionConstraint = ">= 1.16, < 1.28"
|
||||
)
|
||||
|
||||
var supportedNetworkManagerVersionConstraints = []string{
|
||||
">= 1.16, < 1.27",
|
||||
">= 1.44, < 1.45",
|
||||
}
|
||||
|
||||
type networkManagerDbusConfigurator struct {
|
||||
dbusLinkObject dbus.ObjectPath
|
||||
routingAll bool
|
||||
@@ -71,19 +77,19 @@ func (s networkManagerConnSettings) cleanDeprecatedSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
func newNetworkManagerDbusConfigurator(wgInterface WGIface) (hostManager, error) {
|
||||
func newNetworkManagerDbusConfigurator(wgInterface string) (hostManager, error) {
|
||||
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusObjectNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("get nm dbus: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
var s string
|
||||
err = obj.Call(networkManagerDbusGetDeviceByIPIfaceMethod, dbusDefaultFlag, wgInterface.Name()).Store(&s)
|
||||
err = obj.Call(networkManagerDbusGetDeviceByIPIfaceMethod, dbusDefaultFlag, wgInterface).Store(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("call: %w", err)
|
||||
}
|
||||
|
||||
log.Debugf("got network manager dbus Link Object: %s from net interface %s", s, wgInterface.Name())
|
||||
log.Debugf("got network manager dbus Link Object: %s from net interface %s", s, wgInterface)
|
||||
|
||||
return &networkManagerDbusConfigurator{
|
||||
dbusLinkObject: dbus.ObjectPath(s),
|
||||
@@ -97,14 +103,14 @@ func (n *networkManagerDbusConfigurator) supportCustomPort() bool {
|
||||
func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
connSettings, configVersion, err := n.getAppliedConnectionSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while retrieving the applied connection settings, error: %s", err)
|
||||
return fmt.Errorf("retrieving the applied connection settings, error: %w", err)
|
||||
}
|
||||
|
||||
connSettings.cleanDeprecatedSettings()
|
||||
|
||||
dnsIP, err := netip.ParseAddr(config.ServerIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
||||
return fmt.Errorf("unable to parse ip address, error: %w", err)
|
||||
}
|
||||
convDNSIP := binary.LittleEndian.Uint32(dnsIP.AsSlice())
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSKey] = dbus.MakeVariant([]uint32{convDNSIP})
|
||||
@@ -145,23 +151,37 @@ func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig) er
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSPriorityKey] = dbus.MakeVariant(priority)
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSSearchKey] = dbus.MakeVariant(newDomainList)
|
||||
|
||||
// create a backup for unclean shutdown detection before adding domains, as these might end up in the resolv.conf file.
|
||||
// The file content itself is not important for network-manager restoration
|
||||
if err := createUncleanShutdownIndicator(defaultResolvConfPath, networkManager, dnsIP.String()); err != nil {
|
||||
log.Errorf("failed to create unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
log.Infof("adding %d search domains and %d match domains. Search list: %s , Match list: %s", len(searchDomains), len(matchDomains), searchDomains, matchDomains)
|
||||
err = n.reApplyConnectionSettings(connSettings, configVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while reapplying the connection with new settings, error: %s", err)
|
||||
return fmt.Errorf("reapplying the connection with new settings, error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *networkManagerDbusConfigurator) restoreHostDNS() error {
|
||||
// once the interface is gone network manager cleans all config associated with it
|
||||
return n.deleteConnectionSettings()
|
||||
if err := n.deleteConnectionSettings(); err != nil {
|
||||
return fmt.Errorf("delete connection settings: %w", err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *networkManagerDbusConfigurator) getAppliedConnectionSettings() (networkManagerConnSettings, networkManagerConfigVersion, error) {
|
||||
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
||||
return nil, 0, fmt.Errorf("attempting to retrieve the applied connection settings, err: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
@@ -176,7 +196,7 @@ func (n *networkManagerDbusConfigurator) getAppliedConnectionSettings() (network
|
||||
err = obj.CallWithContext(ctx, networkManagerDbusDeviceGetAppliedConnectionMethod, dbusDefaultFlag,
|
||||
networkManagerDbusDefaultBehaviorFlag).Store(&connSettings, &configVersion)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("got error while calling GetAppliedConnection method with context, err: %s", err)
|
||||
return nil, 0, fmt.Errorf("calling GetAppliedConnection method with context, err: %w", err)
|
||||
}
|
||||
|
||||
return connSettings, configVersion, nil
|
||||
@@ -185,7 +205,7 @@ func (n *networkManagerDbusConfigurator) getAppliedConnectionSettings() (network
|
||||
func (n *networkManagerDbusConfigurator) reApplyConnectionSettings(connSettings networkManagerConnSettings, configVersion networkManagerConfigVersion) error {
|
||||
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
||||
return fmt.Errorf("attempting to retrieve the applied connection settings, err: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
@@ -195,7 +215,7 @@ func (n *networkManagerDbusConfigurator) reApplyConnectionSettings(connSettings
|
||||
err = obj.CallWithContext(ctx, networkManagerDbusDeviceReapplyMethod, dbusDefaultFlag,
|
||||
connSettings, configVersion, networkManagerDbusDefaultBehaviorFlag).Store()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while calling ReApply method with context, err: %s", err)
|
||||
return fmt.Errorf("calling ReApply method with context, err: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -204,21 +224,34 @@ func (n *networkManagerDbusConfigurator) reApplyConnectionSettings(connSettings
|
||||
func (n *networkManagerDbusConfigurator) deleteConnectionSettings() error {
|
||||
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
||||
return fmt.Errorf("attempting to retrieve the applied connection settings, err: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// this call is required to remove the device for DNS cleanup, even if it fails
|
||||
err = obj.CallWithContext(ctx, networkManagerDbusDeviceDeleteMethod, dbusDefaultFlag).Store()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while calling delete method with context, err: %s", err)
|
||||
var dbusErr dbus.Error
|
||||
if errors.As(err, &dbusErr) && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name {
|
||||
// interface is gone already
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("calling delete method with context, err: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *networkManagerDbusConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
if err := n.restoreHostDNS(); err != nil {
|
||||
return fmt.Errorf("restoring dns via network-manager: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isNetworkManagerSupported() bool {
|
||||
return isNetworkManagerSupportedVersion() && isNetworkManagerSupportedMode()
|
||||
}
|
||||
@@ -250,13 +283,13 @@ func isNetworkManagerSupportedMode() bool {
|
||||
func getNetworkManagerDNSProperty(property string, store any) error {
|
||||
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusDNSManagerObjectNode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the network manager dns manager object, error: %s", err)
|
||||
return fmt.Errorf("attempting to retrieve the network manager dns manager object, error: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
v, e := obj.GetProperty(property)
|
||||
if e != nil {
|
||||
return fmt.Errorf("got an error getting property %s: %v", property, e)
|
||||
return fmt.Errorf("getting property %s: %w", property, e)
|
||||
}
|
||||
|
||||
return v.Store(store)
|
||||
@@ -278,15 +311,26 @@ func isNetworkManagerSupportedVersion() bool {
|
||||
}
|
||||
versionValue, err := parseVersion(value.Value().(string))
|
||||
if err != nil {
|
||||
log.Errorf("nm: parse version: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
constraints, err := version.NewConstraint(supportedNetworkManagerVersionConstraint)
|
||||
if err != nil {
|
||||
return false
|
||||
var supported bool
|
||||
for _, constraint := range supportedNetworkManagerVersionConstraints {
|
||||
constr, err := version.NewConstraint(constraint)
|
||||
if err != nil {
|
||||
log.Errorf("nm: create constraint: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if met := constr.Check(versionValue); met {
|
||||
supported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return constraints.Check(versionValue)
|
||||
log.Debugf("network manager constraints [%s] met: %t", strings.Join(supportedNetworkManagerVersionConstraints, " | "), supported)
|
||||
return supported
|
||||
}
|
||||
|
||||
func parseVersion(inputVersion string) (*version.Version, error) {
|
||||
|
||||
@@ -5,6 +5,7 @@ package dns
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -21,14 +22,14 @@ type resolvconf struct {
|
||||
}
|
||||
|
||||
// supported "openresolv" only
|
||||
func newResolvConfConfigurator(wgInterface WGIface) (hostManager, error) {
|
||||
func newResolvConfConfigurator(wgInterface string) (hostManager, error) {
|
||||
resolvConfEntries, err := parseDefaultResolvConf()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.Errorf("could not read original search domains from %s: %s", defaultResolvConfPath, err)
|
||||
}
|
||||
|
||||
return &resolvconf{
|
||||
ifaceName: wgInterface.Name(),
|
||||
ifaceName: wgInterface,
|
||||
originalSearchDomains: resolvConfEntries.searchDomains,
|
||||
originalNameServers: resolvConfEntries.nameServers,
|
||||
othersConfigs: resolvConfEntries.others,
|
||||
@@ -44,7 +45,7 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
|
||||
if !config.RouteAll {
|
||||
err = r.restoreHostDNS()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.Errorf("restore host dns: %s", err)
|
||||
}
|
||||
return fmt.Errorf("unable to configure DNS for this peer using resolvconf manager without a nameserver group with all domains configured")
|
||||
}
|
||||
@@ -57,9 +58,14 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
|
||||
append([]string{config.ServerIP}, r.originalNameServers...),
|
||||
r.othersConfigs)
|
||||
|
||||
// create a backup for unclean shutdown detection before the resolv.conf is changed
|
||||
if err := createUncleanShutdownIndicator(defaultResolvConfPath, resolvConfManager, config.ServerIP); err != nil {
|
||||
log.Errorf("failed to create unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
err = r.applyConfig(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("apply config: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("added %d search domains. Search list: %s", len(searchDomainList), searchDomainList)
|
||||
@@ -67,20 +73,34 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
|
||||
}
|
||||
|
||||
func (r *resolvconf) restoreHostDNS() error {
|
||||
// openresolv only, debian resolvconf doesn't support "-f"
|
||||
cmd := exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName)
|
||||
_, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while removing resolvconf configuration for %s interface, error: %s", r.ifaceName, err)
|
||||
return fmt.Errorf("removing resolvconf configuration for %s interface, error: %w", r.ifaceName, err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resolvconf) applyConfig(content bytes.Buffer) error {
|
||||
// openresolv only, debian resolvconf doesn't support "-x"
|
||||
cmd := exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName)
|
||||
cmd.Stdin = &content
|
||||
_, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while applying resolvconf configuration for %s interface, error: %s", r.ifaceName, err)
|
||||
return fmt.Errorf("applying resolvconf configuration for %s interface, error: %w", r.ifaceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resolvconf) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
if err := r.restoreHostDNS(); err != nil {
|
||||
return fmt.Errorf("restoring dns for interface %s: %w", r.ifaceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -31,10 +31,13 @@ func (r *responseWriter) RemoteAddr() net.Addr {
|
||||
func (r *responseWriter) WriteMsg(msg *dns.Msg) error {
|
||||
buff, err := msg.Pack()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("pack: %w", err)
|
||||
}
|
||||
_, err = r.Write(buff)
|
||||
return err
|
||||
|
||||
if _, err := r.Write(buff); err != nil {
|
||||
return fmt.Errorf("write: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes a raw buffer back to the client.
|
||||
|
||||
@@ -142,12 +142,15 @@ func (s *DefaultServer) Initialize() (err error) {
|
||||
if s.permanent {
|
||||
err = s.service.Listen()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("service listen: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.hostManager, err = s.initialize()
|
||||
return err
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialize: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DnsIP returns the DNS resolver server IP address
|
||||
@@ -225,7 +228,7 @@ func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) erro
|
||||
}
|
||||
|
||||
if err := s.applyConfiguration(update); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("apply configuration: %w", err)
|
||||
}
|
||||
|
||||
s.updateSerial = serial
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package dns
|
||||
|
||||
func (s *DefaultServer) initialize() (manager hostManager, err error) {
|
||||
return newHostManager(s.wgInterface)
|
||||
return newHostManager()
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
package dns
|
||||
|
||||
func (s *DefaultServer) initialize() (manager hostManager, err error) {
|
||||
return newHostManager(s.wgInterface)
|
||||
return newHostManager()
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
package dns
|
||||
|
||||
func (s *DefaultServer) initialize() (manager hostManager, err error) {
|
||||
return newHostManager(s.wgInterface)
|
||||
return newHostManager(s.wgInterface.Name())
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type serviceViaListener struct {
|
||||
customAddr *netip.AddrPort
|
||||
server *dns.Server
|
||||
listenIP string
|
||||
listenPort int
|
||||
listenPort uint16
|
||||
listenerIsRunning bool
|
||||
listenerFlagLock sync.Mutex
|
||||
ebpfService ebpfMgr.Manager
|
||||
@@ -63,18 +63,9 @@ func (s *serviceViaListener) Listen() error {
|
||||
s.listenIP, s.listenPort, err = s.evalListenAddress()
|
||||
if err != nil {
|
||||
log.Errorf("failed to eval runtime address: %s", err)
|
||||
return err
|
||||
return fmt.Errorf("eval listen address: %w", err)
|
||||
}
|
||||
s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort)
|
||||
|
||||
if s.shouldApplyPortFwd() {
|
||||
s.ebpfService = ebpf.GetEbpfManagerInstance()
|
||||
err = s.ebpfService.LoadDNSFwd(s.listenIP, s.listenPort)
|
||||
if err != nil {
|
||||
log.Warnf("failed to load DNS port forwarder, custom port may not work well on some Linux operating systems: %s", err)
|
||||
s.ebpfService = nil
|
||||
}
|
||||
}
|
||||
log.Debugf("starting dns on %s", s.server.Addr)
|
||||
go func() {
|
||||
s.setListenerStatus(true)
|
||||
@@ -128,7 +119,7 @@ func (s *serviceViaListener) RuntimePort() int {
|
||||
if s.ebpfService != nil {
|
||||
return defaultPort
|
||||
} else {
|
||||
return s.listenPort
|
||||
return int(s.listenPort)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,54 +131,112 @@ func (s *serviceViaListener) setListenerStatus(running bool) {
|
||||
s.listenerIsRunning = running
|
||||
}
|
||||
|
||||
func (s *serviceViaListener) getFirstListenerAvailable() (string, int, error) {
|
||||
ips := []string{defaultIP, customIP}
|
||||
// evalListenAddress figure out the listen address for the DNS server
|
||||
// first check the 53 port availability on WG interface or lo, if not success
|
||||
// pick a random port on WG interface for eBPF, if not success
|
||||
// check the 5053 port availability on WG interface or lo without eBPF usage,
|
||||
func (s *serviceViaListener) evalListenAddress() (string, uint16, error) {
|
||||
if s.customAddr != nil {
|
||||
return s.customAddr.Addr().String(), s.customAddr.Port(), nil
|
||||
}
|
||||
|
||||
ip, ok := s.testFreePort(defaultPort)
|
||||
if ok {
|
||||
return ip, defaultPort, nil
|
||||
}
|
||||
|
||||
ebpfSrv, port, ok := s.tryToUseeBPF()
|
||||
if ok {
|
||||
s.ebpfService = ebpfSrv
|
||||
return s.wgInterface.Address().IP.String(), port, nil
|
||||
}
|
||||
|
||||
ip, ok = s.testFreePort(customPort)
|
||||
if ok {
|
||||
return ip, customPort, nil
|
||||
}
|
||||
|
||||
return "", 0, fmt.Errorf("failed to find a free port for DNS server")
|
||||
}
|
||||
|
||||
func (s *serviceViaListener) testFreePort(port int) (string, bool) {
|
||||
var ips []string
|
||||
if runtime.GOOS != "darwin" {
|
||||
ips = append([]string{s.wgInterface.Address().IP.String()}, ips...)
|
||||
ips = []string{s.wgInterface.Address().IP.String(), defaultIP, customIP}
|
||||
} else {
|
||||
ips = []string{defaultIP, customIP}
|
||||
}
|
||||
ports := []int{defaultPort, customPort}
|
||||
for _, port := range ports {
|
||||
for _, ip := range ips {
|
||||
addrString := fmt.Sprintf("%s:%d", ip, port)
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||
if err == nil {
|
||||
err = probeListener.Close()
|
||||
if err != nil {
|
||||
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||
}
|
||||
return ip, port, nil
|
||||
}
|
||||
log.Warnf("binding dns on %s is not available, error: %s", addrString, err)
|
||||
|
||||
for _, ip := range ips {
|
||||
if !s.tryToBind(ip, port) {
|
||||
continue
|
||||
}
|
||||
|
||||
return ip, true
|
||||
}
|
||||
return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports)
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (s *serviceViaListener) evalListenAddress() (string, int, error) {
|
||||
if s.customAddr != nil {
|
||||
return s.customAddr.Addr().String(), int(s.customAddr.Port()), nil
|
||||
}
|
||||
|
||||
return s.getFirstListenerAvailable()
|
||||
}
|
||||
|
||||
// shouldApplyPortFwd decides whether to apply eBPF program to capture DNS traffic on port 53.
|
||||
// This is needed because on some operating systems if we start a DNS server not on a default port 53, the domain name
|
||||
// resolution won't work.
|
||||
// So, in case we are running on Linux and picked a non-default port (53) we should fall back to the eBPF solution that will capture
|
||||
// traffic on port 53 and forward it to a local DNS server running on 5053.
|
||||
func (s *serviceViaListener) shouldApplyPortFwd() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
func (s *serviceViaListener) tryToBind(ip string, port int) bool {
|
||||
addrString := fmt.Sprintf("%s:%d", ip, port)
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
log.Warnf("binding dns on %s is not available, error: %s", addrString, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if s.customAddr != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.listenPort == defaultPort {
|
||||
return false
|
||||
err = probeListener.Close()
|
||||
if err != nil {
|
||||
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// tryToUseeBPF decides whether to apply eBPF program to capture DNS traffic on port 53.
|
||||
// This is needed because on some operating systems if we start a DNS server not on a default port 53,
|
||||
// the domain name resolution won't work. So, in case we are running on Linux and picked a free
|
||||
// port we should fall back to the eBPF solution that will capture traffic on port 53 and forward
|
||||
// it to a local DNS server running on the chosen port.
|
||||
func (s *serviceViaListener) tryToUseeBPF() (ebpfMgr.Manager, uint16, bool) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
port, err := s.generateFreePort() //nolint:staticcheck,unused
|
||||
if err != nil {
|
||||
log.Warnf("failed to generate a free port for eBPF DNS forwarder server: %s", err)
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
ebpfSrv := ebpf.GetEbpfManagerInstance()
|
||||
err = ebpfSrv.LoadDNSFwd(s.wgInterface.Address().IP.String(), int(port))
|
||||
if err != nil {
|
||||
log.Warnf("failed to load DNS forwarder eBPF program, error: %s", err)
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
return ebpfSrv, port, true
|
||||
}
|
||||
|
||||
func (s *serviceViaListener) generateFreePort() (uint16, error) {
|
||||
ok := s.tryToBind(s.wgInterface.Address().IP.String(), customPort)
|
||||
if ok {
|
||||
return customPort, nil
|
||||
}
|
||||
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort("0.0.0.0:0"))
|
||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
log.Debugf("failed to bind random port for DNS: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
addrPort := netip.MustParseAddrPort(probeListener.LocalAddr().String()) // might panic if address is incorrect
|
||||
err = probeListener.Close()
|
||||
if err != nil {
|
||||
log.Debugf("failed to free up DNS port: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
return addrPort.Port(), nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (s *serviceViaMemory) Listen() error {
|
||||
var err error
|
||||
s.udpFilterHookID, err = s.filterDNSTraffic()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("filter dns traffice: %w", err)
|
||||
}
|
||||
s.listenerIsRunning = true
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -30,6 +31,8 @@ const (
|
||||
systemdDbusSetDefaultRouteMethodSuffix = systemdDbusLinkInterface + ".SetDefaultRoute"
|
||||
systemdDbusSetDomainsMethodSuffix = systemdDbusLinkInterface + ".SetDomains"
|
||||
systemdDbusResolvConfModeForeign = "foreign"
|
||||
|
||||
dbusErrorUnknownObject = "org.freedesktop.DBus.Error.UnknownObject"
|
||||
)
|
||||
|
||||
type systemdDbusConfigurator struct {
|
||||
@@ -52,22 +55,22 @@ type systemdDbusLinkDomainsInput struct {
|
||||
MatchOnly bool
|
||||
}
|
||||
|
||||
func newSystemdDbusConfigurator(wgInterface WGIface) (hostManager, error) {
|
||||
iface, err := net.InterfaceByName(wgInterface.Name())
|
||||
func newSystemdDbusConfigurator(wgInterface string) (hostManager, error) {
|
||||
iface, err := net.InterfaceByName(wgInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("get interface: %w", err)
|
||||
}
|
||||
|
||||
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("get dbus resolved dest: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
var s string
|
||||
err = obj.Call(systemdDbusGetLinkMethod, dbusDefaultFlag, iface.Index).Store(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("get dbus link method: %w", err)
|
||||
}
|
||||
|
||||
log.Debugf("got dbus Link interface: %s from net interface %s and index %d", s, iface.Name, iface.Index)
|
||||
@@ -84,7 +87,7 @@ func (s *systemdDbusConfigurator) supportCustomPort() bool {
|
||||
func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
parsedIP, err := netip.ParseAddr(config.ServerIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
||||
return fmt.Errorf("unable to parse ip address, error: %w", err)
|
||||
}
|
||||
ipAs4 := parsedIP.As4()
|
||||
defaultLinkInput := systemdDbusDNSInput{
|
||||
@@ -93,7 +96,7 @@ func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
}
|
||||
err = s.callLinkMethod(systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{defaultLinkInput})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting the interface DNS server %s:%d failed with error: %s", config.ServerIP, config.ServerPort, err)
|
||||
return fmt.Errorf("setting the interface DNS server %s:%d failed with error: %w", config.ServerIP, config.ServerPort, err)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -121,7 +124,7 @@ func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
log.Infof("configured %s:%d as main DNS forwarder for this peer", config.ServerIP, config.ServerPort)
|
||||
err = s.callLinkMethod(systemdDbusSetDefaultRouteMethodSuffix, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting link as default dns router, failed with error: %s", err)
|
||||
return fmt.Errorf("setting link as default dns router, failed with error: %w", err)
|
||||
}
|
||||
domainsInput = append(domainsInput, systemdDbusLinkDomainsInput{
|
||||
Domain: nbdns.RootZone,
|
||||
@@ -132,6 +135,12 @@ func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
log.Infof("removing %s:%d as main DNS forwarder for this peer", config.ServerIP, config.ServerPort)
|
||||
}
|
||||
|
||||
// create a backup for unclean shutdown detection before adding domains, as these might end up in the resolv.conf file.
|
||||
// The file content itself is not important for systemd restoration
|
||||
if err := createUncleanShutdownIndicator(defaultResolvConfPath, systemdManager, parsedIP.String()); err != nil {
|
||||
log.Errorf("failed to create unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
log.Infof("adding %d search domains and %d match domains. Search list: %s , Match list: %s", len(searchDomains), len(matchDomains), searchDomains, matchDomains)
|
||||
err = s.setDomainsForInterface(domainsInput)
|
||||
if err != nil {
|
||||
@@ -143,7 +152,7 @@ func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||
func (s *systemdDbusConfigurator) setDomainsForInterface(domainsInput []systemdDbusLinkDomainsInput) error {
|
||||
err := s.callLinkMethod(systemdDbusSetDomainsMethodSuffix, domainsInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting domains configuration failed with error: %s", err)
|
||||
return fmt.Errorf("setting domains configuration failed with error: %w", err)
|
||||
}
|
||||
return s.flushCaches()
|
||||
}
|
||||
@@ -153,17 +162,29 @@ func (s *systemdDbusConfigurator) restoreHostDNS() error {
|
||||
if !isDbusListenerRunning(systemdResolvedDest, s.dbusLinkObject) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// this call is required for DNS cleanup, even if it fails
|
||||
err := s.callLinkMethod(systemdDbusRevertMethodSuffix, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to revert link configuration, got error: %s", err)
|
||||
var dbusErr dbus.Error
|
||||
if errors.As(err, &dbusErr) && dbusErr.Name == dbusErrorUnknownObject {
|
||||
// interface is gone already
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to revert link configuration, got error: %w", err)
|
||||
}
|
||||
|
||||
if err := removeUncleanShutdownIndicator(); err != nil {
|
||||
log.Errorf("failed to remove unclean shutdown resolv.conf backup: %s", err)
|
||||
}
|
||||
|
||||
return s.flushCaches()
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) flushCaches() error {
|
||||
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the object %s, err: %s", systemdDbusObjectNode, err)
|
||||
return fmt.Errorf("attempting to retrieve the object %s, err: %w", systemdDbusObjectNode, err)
|
||||
}
|
||||
defer closeConn()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
@@ -171,7 +192,7 @@ func (s *systemdDbusConfigurator) flushCaches() error {
|
||||
|
||||
err = obj.CallWithContext(ctx, systemdDbusFlushCachesMethod, dbusDefaultFlag).Store()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while calling the FlushCaches method with context, err: %s", err)
|
||||
return fmt.Errorf("calling the FlushCaches method with context, err: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -180,7 +201,7 @@ func (s *systemdDbusConfigurator) flushCaches() error {
|
||||
func (s *systemdDbusConfigurator) callLinkMethod(method string, value any) error {
|
||||
obj, closeConn, err := getDbusObject(systemdResolvedDest, s.dbusLinkObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the object, err: %s", err)
|
||||
return fmt.Errorf("attempting to retrieve the object, err: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
@@ -194,22 +215,29 @@ func (s *systemdDbusConfigurator) callLinkMethod(method string, value any) error
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while calling command with context, err: %s", err)
|
||||
return fmt.Errorf("calling command with context, err: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
|
||||
if err := s.restoreHostDNS(); err != nil {
|
||||
return fmt.Errorf("restoring dns via systemd: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSystemdDbusProperty(property string, store any) error {
|
||||
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the systemd dns manager object, error: %s", err)
|
||||
return fmt.Errorf("attempting to retrieve the systemd dns manager object, error: %w", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
v, e := obj.GetProperty(property)
|
||||
if e != nil {
|
||||
return fmt.Errorf("got an error getting property %s: %v", property, e)
|
||||
return fmt.Errorf("getting property %s: %w", property, e)
|
||||
}
|
||||
|
||||
return v.Store(store)
|
||||
|
||||
5
client/internal/dns/unclean_shutdown_android.go
Normal file
5
client/internal/dns/unclean_shutdown_android.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package dns
|
||||
|
||||
func CheckUncleanShutdown(string) error {
|
||||
return nil
|
||||
}
|
||||
59
client/internal/dns/unclean_shutdown_darwin.go
Normal file
59
client/internal/dns/unclean_shutdown_darwin.go
Normal file
@@ -0,0 +1,59 @@
|
||||
//go:build !ios
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const fileUncleanShutdownFileLocation = "/var/lib/netbird/unclean_shutdown_dns"
|
||||
|
||||
func CheckUncleanShutdown(string) error {
|
||||
if _, err := os.Stat(fileUncleanShutdownFileLocation); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// no file -> clean shutdown
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Warnf("detected unclean shutdown, file %s exists. Restoring unclean shutdown dns settings.", fileUncleanShutdownFileLocation)
|
||||
|
||||
manager, err := newHostManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create host manager: %w", err)
|
||||
}
|
||||
|
||||
if err := manager.restoreUncleanShutdownDNS(nil); err != nil {
|
||||
return fmt.Errorf("restore unclean shutdown backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUncleanShutdownIndicator() error {
|
||||
dir := filepath.Dir(fileUncleanShutdownFileLocation)
|
||||
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
||||
return fmt.Errorf("create dir %s: %w", dir, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(fileUncleanShutdownFileLocation, nil, 0644); err != nil { //nolint:gosec
|
||||
return fmt.Errorf("create %s: %w", fileUncleanShutdownFileLocation, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUncleanShutdownIndicator() error {
|
||||
if err := os.Remove(fileUncleanShutdownFileLocation); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("remove %s: %w", fileUncleanShutdownFileLocation, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
5
client/internal/dns/unclean_shutdown_ios.go
Normal file
5
client/internal/dns/unclean_shutdown_ios.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package dns
|
||||
|
||||
func CheckUncleanShutdown(string) error {
|
||||
return nil
|
||||
}
|
||||
96
client/internal/dns/unclean_shutdown_linux.go
Normal file
96
client/internal/dns/unclean_shutdown_linux.go
Normal file
@@ -0,0 +1,96 @@
|
||||
//go:build !android
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
fileUncleanShutdownResolvConfLocation = "/var/lib/netbird/resolv.conf"
|
||||
fileUncleanShutdownManagerTypeLocation = "/var/lib/netbird/manager"
|
||||
)
|
||||
|
||||
func CheckUncleanShutdown(wgIface string) error {
|
||||
if _, err := os.Stat(fileUncleanShutdownResolvConfLocation); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// no file -> clean shutdown
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Warnf("detected unclean shutdown, file %s exists", fileUncleanShutdownResolvConfLocation)
|
||||
|
||||
managerData, err := os.ReadFile(fileUncleanShutdownManagerTypeLocation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", fileUncleanShutdownManagerTypeLocation, err)
|
||||
}
|
||||
|
||||
managerFields := strings.Split(string(managerData), ",")
|
||||
if len(managerFields) < 2 {
|
||||
return errors.New("split manager data: insufficient number of fields")
|
||||
}
|
||||
osManagerTypeStr, dnsAddressStr := managerFields[0], managerFields[1]
|
||||
|
||||
dnsAddress, err := netip.ParseAddr(dnsAddressStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse dns address %s failed: %w", dnsAddressStr, err)
|
||||
}
|
||||
|
||||
log.Warnf("restoring unclean shutdown dns settings via previously detected manager: %s", osManagerTypeStr)
|
||||
|
||||
// determine os manager type, so we can invoke the respective restore action
|
||||
osManagerType, err := newOsManagerType(osManagerTypeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("detect previous host manager: %w", err)
|
||||
}
|
||||
|
||||
manager, err := newHostManagerFromType(wgIface, osManagerType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create previous host manager: %w", err)
|
||||
}
|
||||
|
||||
if err := manager.restoreUncleanShutdownDNS(&dnsAddress); err != nil {
|
||||
return fmt.Errorf("restore unclean shutdown backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUncleanShutdownIndicator(sourcePath string, managerType osManagerType, dnsAddress string) error {
|
||||
dir := filepath.Dir(fileUncleanShutdownResolvConfLocation)
|
||||
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
||||
return fmt.Errorf("create dir %s: %w", dir, err)
|
||||
}
|
||||
|
||||
if err := copyFile(sourcePath, fileUncleanShutdownResolvConfLocation); err != nil {
|
||||
return fmt.Errorf("create %s: %w", sourcePath, err)
|
||||
}
|
||||
|
||||
managerData := fmt.Sprintf("%s,%s", managerType, dnsAddress)
|
||||
|
||||
if err := os.WriteFile(fileUncleanShutdownManagerTypeLocation, []byte(managerData), 0644); err != nil { //nolint:gosec
|
||||
return fmt.Errorf("create %s: %w", fileUncleanShutdownManagerTypeLocation, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUncleanShutdownIndicator() error {
|
||||
if err := os.Remove(fileUncleanShutdownResolvConfLocation); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("remove %s: %w", fileUncleanShutdownResolvConfLocation, err)
|
||||
}
|
||||
if err := os.Remove(fileUncleanShutdownManagerTypeLocation); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("remove %s: %w", fileUncleanShutdownManagerTypeLocation, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
75
client/internal/dns/unclean_shutdown_windows.go
Normal file
75
client/internal/dns/unclean_shutdown_windows.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
netbirdProgramDataLocation = "Netbird"
|
||||
fileUncleanShutdownFile = "unclean_shutdown_dns.txt"
|
||||
)
|
||||
|
||||
func CheckUncleanShutdown(string) error {
|
||||
file := getUncleanShutdownFile()
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// no file -> clean shutdown
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Warnf("detected unclean shutdown, file %s exists. Restoring unclean shutdown dns settings.", file)
|
||||
|
||||
guid, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", file, err)
|
||||
}
|
||||
|
||||
manager, err := newHostManagerWithGuid(string(guid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create host manager: %w", err)
|
||||
}
|
||||
|
||||
if err := manager.restoreUncleanShutdownDNS(nil); err != nil {
|
||||
return fmt.Errorf("restore unclean shutdown backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUncleanShutdownIndicator(guid string) error {
|
||||
file := getUncleanShutdownFile()
|
||||
|
||||
dir := filepath.Dir(file)
|
||||
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
||||
return fmt.Errorf("create dir %s: %w", dir, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(file, []byte(guid), 0600); err != nil {
|
||||
return fmt.Errorf("create %s: %w", file, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUncleanShutdownIndicator() error {
|
||||
file := getUncleanShutdownFile()
|
||||
|
||||
if err := os.Remove(file); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("remove %s: %w", file, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUncleanShutdownFile() string {
|
||||
return filepath.Join(os.Getenv("PROGRAMDATA"), netbirdProgramDataLocation, fileUncleanShutdownFile)
|
||||
}
|
||||
@@ -25,8 +25,7 @@ const (
|
||||
const testRecord = "."
|
||||
|
||||
type upstreamClient interface {
|
||||
exchange(upstream string, r *dns.Msg) (*dns.Msg, time.Duration, error)
|
||||
exchangeContext(ctx context.Context, upstream string, r *dns.Msg) (*dns.Msg, time.Duration, error)
|
||||
exchange(ctx context.Context, upstream string, r *dns.Msg) (*dns.Msg, time.Duration, error)
|
||||
}
|
||||
|
||||
type UpstreamResolver interface {
|
||||
@@ -80,8 +79,15 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}
|
||||
|
||||
for _, upstream := range u.upstreamServers {
|
||||
var rm *dns.Msg
|
||||
var t time.Duration
|
||||
var err error
|
||||
|
||||
rm, t, err := u.upstreamClient.exchange(upstream, r)
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
||||
defer cancel()
|
||||
rm, t, err = u.upstreamClient.exchange(ctx, upstream, r)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || isTimeout(err) {
|
||||
@@ -213,7 +219,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
|
||||
}
|
||||
|
||||
log.Tracef("checking connectivity with upstreams %s failed. Retrying in %s", u.upstreamServers, exponentialBackOff.NextBackOff())
|
||||
return fmt.Errorf("got an error from upstream check call")
|
||||
return fmt.Errorf("upstream check call error")
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, exponentialBackOff)
|
||||
@@ -259,6 +265,6 @@ func (u *upstreamResolverBase) testNameserver(server string) error {
|
||||
|
||||
r := new(dns.Msg).SetQuestion(testRecord, dns.TypeSOA)
|
||||
|
||||
_, _, err := u.upstreamClient.exchangeContext(ctx, server, r)
|
||||
_, _, err := u.upstreamClient.exchange(ctx, server, r)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,34 +40,38 @@ func newUpstreamResolver(parentCTX context.Context, interfaceName string, ip net
|
||||
return ios, nil
|
||||
}
|
||||
|
||||
func (u *upstreamResolverIOS) exchange(upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||
return u.exchangeContext(context.Background(), upstream, r)
|
||||
}
|
||||
|
||||
func (u *upstreamResolverIOS) exchangeContext(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||
func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||
client := &dns.Client{}
|
||||
upstreamHost, _, err := net.SplitHostPort(upstream)
|
||||
if err != nil {
|
||||
log.Errorf("error while parsing upstream host: %s", err)
|
||||
}
|
||||
|
||||
timeout := upstreamTimeout
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeout = time.Until(deadline)
|
||||
}
|
||||
client.DialTimeout = timeout
|
||||
|
||||
upstreamIP := net.ParseIP(upstreamHost)
|
||||
if u.lNet.Contains(upstreamIP) || net.IP.IsPrivate(upstreamIP) {
|
||||
log.Debugf("using private client to query upstream: %s", upstream)
|
||||
client = u.getClientPrivate()
|
||||
client = u.getClientPrivate(timeout)
|
||||
}
|
||||
|
||||
return client.ExchangeContext(ctx, r, upstream)
|
||||
// Cannot use client.ExchangeContext because it overwrites our Dialer
|
||||
return client.Exchange(r, upstream)
|
||||
}
|
||||
|
||||
// getClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface
|
||||
// This method is needed for iOS
|
||||
func (u *upstreamResolverIOS) getClientPrivate() *dns.Client {
|
||||
func (u *upstreamResolverIOS) getClientPrivate(dialTimeout time.Duration) *dns.Client {
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.UDPAddr{
|
||||
IP: u.lIP,
|
||||
Port: 0, // Let the OS pick a free port
|
||||
},
|
||||
Timeout: upstreamTimeout,
|
||||
Timeout: dialTimeout,
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
var operr error
|
||||
fn := func(s uintptr) {
|
||||
|
||||
@@ -23,14 +23,7 @@ func newUpstreamResolver(parentCTX context.Context, interfaceName string, ip net
|
||||
return nonIOS, nil
|
||||
}
|
||||
|
||||
func (u *upstreamResolverNonIOS) exchange(upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||
// default upstream timeout
|
||||
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
||||
defer cancel()
|
||||
return u.exchangeContext(ctx, upstream, r)
|
||||
}
|
||||
|
||||
func (u *upstreamResolverNonIOS) exchangeContext(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) {
|
||||
func (u *upstreamResolverNonIOS) exchange(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)
|
||||
}
|
||||
|
||||
@@ -105,13 +105,8 @@ type mockUpstreamResolver struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Exchange mock implementation of Exchangefrom upstreamResolver
|
||||
func (c mockUpstreamResolver) exchange(upstream string, r *dns.Msg) (*dns.Msg, time.Duration, error) {
|
||||
return c.exchangeContext(context.Background(), upstream, r)
|
||||
}
|
||||
|
||||
// ExchangeContext mock implementation of ExchangeContext from upstreamResolver
|
||||
func (c mockUpstreamResolver) exchangeContext(_ context.Context, _ string, _ *dns.Msg) (*dns.Msg, time.Duration, error) {
|
||||
// exchange mock implementation of exchange from upstreamResolver
|
||||
func (c mockUpstreamResolver) exchange(_ context.Context, _ string, _ *dns.Msg) (*dns.Msg, time.Duration, error) {
|
||||
return c.r, c.rtt, c.err
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const (
|
||||
)
|
||||
|
||||
func (tf *GeneralManager) LoadDNSFwd(ip string, dnsPort int) error {
|
||||
log.Debugf("load ebpf DNS forwarder: address: %s:%d", ip, dnsPort)
|
||||
log.Debugf("load eBPF DNS forwarder, watching addr: %s:53, redirect to port: %d", ip, dnsPort)
|
||||
tf.lock.Lock()
|
||||
defer tf.lock.Unlock()
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Debug
|
||||
# DNS forwarder
|
||||
|
||||
The agent attach the XDP program to the lo device. We can not use fake address in eBPF because the
|
||||
traffic does not appear in the eBPF program. The program capture the traffic on wg_ip:53 and
|
||||
overwrite in it the destination port to 5053.
|
||||
|
||||
# Debug
|
||||
|
||||
The CONFIG_BPF_EVENTS kernel module is required for bpf_printk.
|
||||
Apply this code to use bpf_printk
|
||||
|
||||
@@ -1080,6 +1080,11 @@ func (e *Engine) close() {
|
||||
log.Errorf("failed closing ebpf proxy: %s", err)
|
||||
}
|
||||
|
||||
// stop/restore DNS first so dbus and friends don't complain because of a missing interface
|
||||
if e.dnsServer != nil {
|
||||
e.dnsServer.Stop()
|
||||
}
|
||||
|
||||
log.Debugf("removing Netbird interface %s", e.config.WgIfaceName)
|
||||
if e.wgInterface != nil {
|
||||
if err := e.wgInterface.Close(); err != nil {
|
||||
@@ -1098,10 +1103,6 @@ func (e *Engine) close() {
|
||||
e.routeManager.Stop()
|
||||
}
|
||||
|
||||
if e.dnsServer != nil {
|
||||
e.dnsServer.Stop()
|
||||
}
|
||||
|
||||
if e.firewall != nil {
|
||||
err := e.firewall.Reset()
|
||||
if err != nil {
|
||||
|
||||
@@ -130,8 +130,9 @@ type Conn struct {
|
||||
remoteModeCh chan ModeMessage
|
||||
meta meta
|
||||
|
||||
adapter iface.TunAdapter
|
||||
iFaceDiscover stdnet.ExternalIFaceDiscover
|
||||
adapter iface.TunAdapter
|
||||
iFaceDiscover stdnet.ExternalIFaceDiscover
|
||||
sentExtraSrflx bool
|
||||
}
|
||||
|
||||
// meta holds meta information about a connection
|
||||
@@ -464,6 +465,8 @@ func (conn *Conn) cleanup() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
conn.sentExtraSrflx = false
|
||||
|
||||
var err1, err2, err3 error
|
||||
if conn.agent != nil {
|
||||
err1 = conn.agent.Close()
|
||||
@@ -557,6 +560,30 @@ func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err)
|
||||
}
|
||||
|
||||
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
|
||||
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
|
||||
if !conn.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
|
||||
relatedAdd := candidate.RelatedAddress()
|
||||
extraSrflx, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
|
||||
Network: candidate.NetworkType().String(),
|
||||
Address: candidate.Address(),
|
||||
Port: relatedAdd.Port,
|
||||
Component: candidate.Component(),
|
||||
RelAddr: relatedAdd.Address,
|
||||
RelPort: relatedAdd.Port,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed creating extra server reflexive candidate %s", err)
|
||||
return
|
||||
}
|
||||
err = conn.signalCandidate(extraSrflx)
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling the extra server reflexive candidate to the remote peer %s: %s", conn.config.Key, err)
|
||||
return
|
||||
}
|
||||
conn.sentExtraSrflx = true
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
// CreateCertManager wraps common logic of generating Let's encrypt certificate.
|
||||
@@ -12,7 +13,7 @@ func CreateCertManager(datadir string, letsencryptDomain string) (*autocert.Mana
|
||||
certDir := filepath.Join(datadir, "letsencrypt")
|
||||
|
||||
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(certDir, os.ModeDir)
|
||||
err = os.MkdirAll(certDir, 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -27,7 +27,7 @@ require (
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
google.golang.org/grpc v1.56.3
|
||||
google.golang.org/protobuf v1.30.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ require (
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240202184442-37827591b26c
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/libp2p/go-netroute v0.2.0
|
||||
@@ -170,3 +171,5 @@ replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-202
|
||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed
|
||||
|
||||
replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||
|
||||
replace github.com/grpc-ecosystem/go-grpc-middleware/v2 => github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f
|
||||
|
||||
5
go.sum
5
go.sum
@@ -517,6 +517,8 @@ 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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f h1:J+egXEDkpg/vOYYzPO5IwF8OufGb7g+KcwEF1AWIzhQ=
|
||||
github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
||||
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/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@@ -940,8 +942,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -39,7 +39,6 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
||||
wgIFace.tun = newTunUSPDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet)
|
||||
wgIFace.userspaceBind = true
|
||||
return wgIFace, nil
|
||||
|
||||
}
|
||||
|
||||
// CreateOnAndroid this function make sense on mobile only
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "3"
|
||||
services:
|
||||
#UI dashboard
|
||||
dashboard:
|
||||
image: wiretrustee/dashboard:$NETBIRD_DASHBOARD_TAG
|
||||
image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
|
||||
@@ -705,7 +705,7 @@ services:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
#UI dashboard
|
||||
dashboard:
|
||||
image: wiretrustee/dashboard:latest
|
||||
image: netbirdio/dashboard:latest
|
||||
restart: unless-stopped
|
||||
networks: [netbird]
|
||||
env_file:
|
||||
|
||||
@@ -7,17 +7,20 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -29,6 +32,7 @@ import (
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
@@ -115,7 +119,7 @@ var (
|
||||
}
|
||||
|
||||
if _, err = os.Stat(config.Datadir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(config.Datadir, os.ModeDir)
|
||||
err = os.MkdirAll(config.Datadir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating datadir: %s: %v", config.Datadir, err)
|
||||
}
|
||||
@@ -167,7 +171,31 @@ var (
|
||||
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
|
||||
gRPCOpts := []grpc.ServerOption{grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)}
|
||||
trustedPeers := config.ReverseProxy.TrustedPeers
|
||||
defaultTrustedPeers := []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}
|
||||
if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) {
|
||||
log.Warn("TrustedPeers are configured to default value '0.0.0.0/0', '::/0'. This allows connection IP spoofing.")
|
||||
trustedPeers = defaultTrustedPeers
|
||||
}
|
||||
trustedHTTPProxies := config.ReverseProxy.TrustedHTTPProxies
|
||||
trustedProxiesCount := config.ReverseProxy.TrustedHTTPProxiesCount
|
||||
if len(trustedHTTPProxies) > 0 && trustedProxiesCount > 0 {
|
||||
log.Warn("TrustedHTTPProxies and TrustedHTTPProxiesCount both are configured. " +
|
||||
"This is not recommended way to extract X-Forwarded-For. Consider using one of these options.")
|
||||
}
|
||||
realipOpts := realip.Opts{
|
||||
TrustedPeers: trustedPeers,
|
||||
TrustedProxies: trustedHTTPProxies,
|
||||
TrustedProxiesCount: trustedProxiesCount,
|
||||
Headers: []string{realip.XForwardedFor, realip.XRealIp},
|
||||
}
|
||||
gRPCOpts := []grpc.ServerOption{
|
||||
grpc.KeepaliveEnforcementPolicy(kaep),
|
||||
grpc.KeepaliveParams(kasp),
|
||||
grpc.ChainUnaryInterceptor(realip.UnaryServerInterceptorOpts(realipOpts)),
|
||||
grpc.ChainStreamInterceptor(realip.StreamServerInterceptorOpts(realipOpts)),
|
||||
}
|
||||
|
||||
var certManager *autocert.Manager
|
||||
var tlsConfig *tls.Config
|
||||
tlsEnabled := false
|
||||
|
||||
@@ -1223,7 +1223,21 @@ func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Accou
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil //nolint:nilnil
|
||||
// add extra check on external cache manager. We may get to this point when the user is not yet findable in IDP,
|
||||
// or it didn't have its metadata updated with am.addAccountIDToIDPAppMeta
|
||||
user, err := account.FindUser(userID)
|
||||
if err != nil {
|
||||
log.Errorf("failed finding user %s in account %s", userID, account.Id)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := user.IntegrationReference.CacheKey(account.Id, userID)
|
||||
ud, err := am.externalCacheManager.Get(am.ctx, key)
|
||||
if err != nil {
|
||||
log.Debugf("failed to get externalCache for key: %s, error: %s", key, err)
|
||||
}
|
||||
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) refreshCache(accountID string) ([]*idp.UserData, error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
@@ -47,6 +48,8 @@ type Config struct {
|
||||
PKCEAuthorizationFlow *PKCEAuthorizationFlow
|
||||
|
||||
StoreConfig StoreConfig
|
||||
|
||||
ReverseProxy ReverseProxy
|
||||
}
|
||||
|
||||
// GetAuthAudiences returns the audience from the http config and device authorization flow config
|
||||
@@ -143,6 +146,27 @@ type StoreConfig struct {
|
||||
Engine StoreEngine
|
||||
}
|
||||
|
||||
// ReverseProxy contains reverse proxy configuration in front of management.
|
||||
type ReverseProxy struct {
|
||||
// TrustedHTTPProxies represents a list of trusted HTTP proxies by their IP prefixes.
|
||||
// When extracting the real IP address from request headers, the middleware will verify
|
||||
// if the peer's address falls within one of these trusted IP prefixes.
|
||||
TrustedHTTPProxies []netip.Prefix
|
||||
|
||||
// TrustedHTTPProxiesCount specifies the count of trusted HTTP proxies between the internet
|
||||
// and the server. When using the trusted proxy count method to extract the real IP address,
|
||||
// the middleware will search the X-Forwarded-For IP list from the rightmost by this count
|
||||
// minus one.
|
||||
TrustedHTTPProxiesCount uint
|
||||
|
||||
// TrustedPeers represents a list of trusted peers by their IP prefixes.
|
||||
// These peers are considered trustworthy by the gRPC server operator,
|
||||
// and the middleware will attempt to extract the real IP address from
|
||||
// request headers if the peer's address falls within one of these
|
||||
// trusted IP prefixes.
|
||||
TrustedPeers []netip.Prefix
|
||||
}
|
||||
|
||||
// validateURL validates input http url
|
||||
func validateURL(httpURL string) bool {
|
||||
_, err := url.ParseRequestURI(httpURL)
|
||||
|
||||
@@ -115,6 +115,13 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, peerID := range newGroup.Peers {
|
||||
if account.Peers[peerID] == nil {
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID \"%s\" not found", peerID)
|
||||
}
|
||||
}
|
||||
|
||||
oldGroup, exists := account.Groups[newGroup.ID]
|
||||
account.Groups[newGroup.ID] = newGroup
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
pb "github.com/golang/protobuf/proto" // nolint
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
gRPCPeer "google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
@@ -109,6 +109,13 @@ func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getRealIP(ctx context.Context) string {
|
||||
if ip, ok := realip.FromContext(ctx); ok {
|
||||
return ip.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
||||
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
||||
func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
||||
@@ -116,10 +123,8 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
|
||||
if s.appMetrics != nil {
|
||||
s.appMetrics.GRPCMetrics().CountSyncRequest()
|
||||
}
|
||||
p, ok := gRPCPeer.FromContext(srv.Context())
|
||||
if ok {
|
||||
log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, p.Addr.String())
|
||||
}
|
||||
realIP := getRealIP(srv.Context())
|
||||
log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, realIP)
|
||||
|
||||
syncReq := &proto.SyncRequest{}
|
||||
peerKey, err := s.parseRequest(req, syncReq)
|
||||
@@ -290,10 +295,8 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
||||
if s.appMetrics != nil {
|
||||
s.appMetrics.GRPCMetrics().CountLoginRequest()
|
||||
}
|
||||
p, ok := gRPCPeer.FromContext(ctx)
|
||||
if ok {
|
||||
log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, p.Addr.String())
|
||||
}
|
||||
realIP := getRealIP(ctx)
|
||||
log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, realIP)
|
||||
|
||||
loginReq := &proto.LoginRequest{}
|
||||
peerKey, err := s.parseRequest(req, loginReq)
|
||||
@@ -303,8 +306,7 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
||||
|
||||
if loginReq.GetMeta() == nil {
|
||||
msg := status.Errorf(codes.FailedPrecondition,
|
||||
"peer system meta has to be provided to log in. Peer %s, remote addr %s", peerKey.String(),
|
||||
p.Addr.String())
|
||||
"peer system meta has to be provided to log in. Peer %s, remote addr %s", peerKey.String(), realIP)
|
||||
log.Warn(msg)
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
@@ -765,15 +765,10 @@ components:
|
||||
description: Policy status
|
||||
type: boolean
|
||||
example: true
|
||||
query:
|
||||
description: Policy Rego query
|
||||
type: string
|
||||
example: "package netbird\\n\\nall[rule] {\\n is_peer_in_any_group([\\\"ch8i4ug6lnn4g9hqv7m0\\\",\\\"ch8i4ug6lnn4g9hqv7m0\\\"])\\n rule := {\\n rules_from_group(\\\"ch8i4ug6lnn4g9hqv7m0\\\", \\\"dst\\\", \\\"accept\\\", \\\"\\\"),\\n rules_from_group(\\\"ch8i4ug6lnn4g9hqv7m0\\\", \\\"src\\\", \\\"accept\\\", \\\"\\\"),\\n }[_][_]\\n}\\n"
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
- enabled
|
||||
- query
|
||||
PolicyUpdate:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PolicyMinimum'
|
||||
@@ -909,7 +904,7 @@ components:
|
||||
nameservers:
|
||||
description: Nameserver list
|
||||
minLength: 1
|
||||
maxLength: 2
|
||||
maxLength: 3
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Nameserver'
|
||||
|
||||
@@ -567,9 +567,6 @@ type Policy struct {
|
||||
// Name Policy name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Query Policy Rego query
|
||||
Query string `json:"query"`
|
||||
|
||||
// Rules Policy rule object for policy UI editor
|
||||
Rules []PolicyRule `json:"rules"`
|
||||
}
|
||||
@@ -587,9 +584,6 @@ type PolicyMinimum struct {
|
||||
|
||||
// Name Policy name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Query Policy Rego query
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// PolicyRule defines model for PolicyRule.
|
||||
@@ -717,9 +711,6 @@ type PolicyUpdate struct {
|
||||
// Name Policy name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Query Policy Rego query
|
||||
Query string `json:"query"`
|
||||
|
||||
// Rules Policy rule object for policy UI editor
|
||||
Rules []PolicyRuleUpdate `json:"rules"`
|
||||
}
|
||||
|
||||
@@ -240,10 +240,9 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
||||
cache := make(map[string]api.PeerMinimum)
|
||||
gr := api.Group{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
Issued: &group.Issued,
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
Issued: &group.Issued,
|
||||
}
|
||||
|
||||
for _, pid := range group.Peers {
|
||||
@@ -261,5 +260,8 @@ func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
||||
gr.Peers = append(gr.Peers, peerResp)
|
||||
}
|
||||
}
|
||||
|
||||
gr.PeersCount = len(gr.Peers)
|
||||
|
||||
return &gr
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -150,19 +152,37 @@ func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, erro
|
||||
|
||||
// getAllUsers returns all users in a Google Workspace account filtered by customer ID.
|
||||
func (gm *GoogleWorkspaceManager) getAllUsers() ([]*UserData, error) {
|
||||
var usersLimit int64 = 500
|
||||
if maxUsersLimitEnv := os.Getenv("GOOGLE_WORKSPACE_USERS_LIMIT"); maxUsersLimitEnv != "" {
|
||||
maxUsersLimit, err := strconv.Atoi(maxUsersLimitEnv)
|
||||
if err == nil {
|
||||
log.Debugf("GOOGLE_WORKSPACE_USERS_LIMIT env is set using %d as users limit", maxUsersLimit)
|
||||
usersLimit = int64(maxUsersLimit)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("GOOGLE_WORKSPACE_USERS_LIMIT env is not set using default users limit 500")
|
||||
}
|
||||
|
||||
users := make([]*UserData, 0)
|
||||
pageToken := ""
|
||||
for {
|
||||
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(500)
|
||||
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(usersLimit)
|
||||
if pageToken != "" {
|
||||
call.PageToken(pageToken)
|
||||
}
|
||||
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
log.Debugf("failed to retrieve users from workspace error: %s, http status: %d, headers: %v",
|
||||
err.Error(),
|
||||
resp.HTTPStatusCode,
|
||||
resp.Header,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("fetched %d users from workspace", len(resp.Users))
|
||||
|
||||
for _, user := range resp.Users {
|
||||
users = append(users, parseGoogleWorkspaceUser(user))
|
||||
}
|
||||
|
||||
@@ -255,8 +255,8 @@ func validateNSGroupName(name, nsGroupID string, nsGroupMap map[string]*nbdns.Na
|
||||
|
||||
func validateNSList(list []nbdns.NameServer) error {
|
||||
nsListLenght := len(list)
|
||||
if nsListLenght == 0 || nsListLenght > 2 {
|
||||
return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 2, got %d", len(list))
|
||||
if nsListLenght == 0 || nsListLenght > 3 {
|
||||
return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 3, got %d", len(list))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ func TestCreateNameServerGroup(t *testing.T) {
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Create A NS Group With More Than 2 Nameservers Should Fail",
|
||||
name: "Create A NS Group With More Than 3 Nameservers Should Fail",
|
||||
inputArgs: input{
|
||||
name: "super",
|
||||
description: "super",
|
||||
@@ -238,6 +238,11 @@ func TestCreateNameServerGroup(t *testing.T) {
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.4.4"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
@@ -457,6 +462,11 @@ func TestSaveNameServerGroup(t *testing.T) {
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.4.4"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
}
|
||||
invalidID := "doesntExist"
|
||||
validName := "12345678901234567890qw"
|
||||
|
||||
@@ -890,18 +890,6 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userData == nil {
|
||||
// lets check external cache
|
||||
key := newUser.IntegrationReference.CacheKey(account.Id, newUser.Id)
|
||||
log.Debugf("looking up user %s of account %s in external cache", key, account.Id)
|
||||
info, err := am.externalCacheManager.Get(am.ctx, key)
|
||||
if err != nil {
|
||||
log.Infof("Get ExternalCache for key: %s, error: %s", key, err)
|
||||
return nil, status.Errorf(status.NotFound, "user %s not found in the IdP", newUser.Id)
|
||||
}
|
||||
|
||||
return newUser.ToUserInfo(info)
|
||||
}
|
||||
return newUser.ToUserInfo(userData)
|
||||
}
|
||||
return newUser.ToUserInfo(nil)
|
||||
|
||||
Reference in New Issue
Block a user