Merge branch 'main' into userspace-router

This commit is contained in:
Viktor Liu
2025-01-14 10:49:04 +01:00
20 changed files with 280 additions and 93 deletions

View File

@@ -0,0 +1,18 @@
//go:build !android
package dns
import (
"github.com/netbirdio/netbird/client/configs"
"os"
"path/filepath"
)
var fileUncleanShutdownResolvConfLocation string
func init() {
fileUncleanShutdownResolvConfLocation = os.Getenv("NB_UNCLEAN_SHUTDOWN_RESOLV_FILE")
if fileUncleanShutdownResolvConfLocation == "" {
fileUncleanShutdownResolvConfLocation = filepath.Join(configs.StateDir, "resolv.conf")
}
}

View File

@@ -1,5 +0,0 @@
package dns
const (
fileUncleanShutdownResolvConfLocation = "/var/db/netbird/resolv.conf"
)

View File

@@ -1,7 +0,0 @@
//go:build !android
package dns
const (
fileUncleanShutdownResolvConfLocation = "/var/lib/netbird/resolv.conf"
)

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"net/netip"
"os/exec"
"strings"
log "github.com/sirupsen/logrus"
@@ -15,23 +16,64 @@ import (
const resolvconfCommand = "resolvconf"
// resolvconfType represents the type of resolvconf implementation
type resolvconfType int
func (r resolvconfType) String() string {
switch r {
case typeOpenresolv:
return "openresolv"
case typeResolvconf:
return "resolvconf"
default:
return "unknown"
}
}
const (
typeOpenresolv resolvconfType = iota
typeResolvconf
)
type resolvconf struct {
ifaceName string
implType resolvconfType
originalSearchDomains []string
originalNameServers []string
othersConfigs []string
}
// supported "openresolv" only
func detectResolvconfType() (resolvconfType, error) {
cmd := exec.Command(resolvconfCommand, "--version")
out, err := cmd.Output()
if err != nil {
return typeOpenresolv, fmt.Errorf("failed to determine resolvconf type: %w", err)
}
if strings.Contains(string(out), "openresolv") {
return typeOpenresolv, nil
}
return typeResolvconf, nil
}
func newResolvConfConfigurator(wgInterface string) (*resolvconf, error) {
resolvConfEntries, err := parseDefaultResolvConf()
if err != nil {
log.Errorf("could not read original search domains from %s: %s", defaultResolvConfPath, err)
}
implType, err := detectResolvconfType()
if err != nil {
log.Warnf("failed to detect resolvconf type, defaulting to openresolv: %v", err)
implType = typeOpenresolv
} else {
log.Infof("detected resolvconf type: %v", implType)
}
return &resolvconf{
ifaceName: wgInterface,
implType: implType,
originalSearchDomains: resolvConfEntries.searchDomains,
originalNameServers: resolvConfEntries.nameServers,
othersConfigs: resolvConfEntries.others,
@@ -80,8 +122,15 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig, stateManager *stateman
}
func (r *resolvconf) restoreHostDNS() error {
// openresolv only, debian resolvconf doesn't support "-f"
cmd := exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName)
var cmd *exec.Cmd
switch r.implType {
case typeOpenresolv:
cmd = exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName)
case typeResolvconf:
cmd = exec.Command(resolvconfCommand, "-d", r.ifaceName)
}
_, err := cmd.Output()
if err != nil {
return fmt.Errorf("removing resolvconf configuration for %s interface: %w", r.ifaceName, err)
@@ -91,10 +140,21 @@ func (r *resolvconf) restoreHostDNS() error {
}
func (r *resolvconf) applyConfig(content bytes.Buffer) error {
// openresolv only, debian resolvconf doesn't support "-x"
cmd := exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName)
var cmd *exec.Cmd
switch r.implType {
case typeOpenresolv:
// OpenResolv supports exclusive mode with -x
cmd = exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName)
case typeResolvconf:
cmd = exec.Command(resolvconfCommand, "-a", r.ifaceName)
default:
return fmt.Errorf("unsupported resolvconf type: %v", r.implType)
}
cmd.Stdin = &content
_, err := cmd.Output()
out, err := cmd.Output()
log.Tracef("resolvconf output: %s", out)
if err != nil {
return fmt.Errorf("applying resolvconf configuration for %s interface: %w", r.ifaceName, err)
}

View File

@@ -772,12 +772,13 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
}
}
e.statusRecorder.UpdateLocalPeerState(peer.LocalPeerState{
IP: e.config.WgAddr,
PubKey: e.config.WgPrivateKey.PublicKey().String(),
KernelInterface: device.WireGuardModuleIsLoaded(),
FQDN: conf.GetFqdn(),
})
state := e.statusRecorder.GetLocalPeerState()
state.IP = e.config.WgAddr
state.PubKey = e.config.WgPrivateKey.PublicKey().String()
state.KernelInterface = device.WireGuardModuleIsLoaded()
state.FQDN = conf.GetFqdn()
e.statusRecorder.UpdateLocalPeerState(state)
return nil
}

View File

@@ -84,6 +84,12 @@ type LocalPeerState struct {
Routes map[string]struct{}
}
// Clone returns a copy of the LocalPeerState
func (l LocalPeerState) Clone() LocalPeerState {
l.Routes = maps.Clone(l.Routes)
return l
}
// SignalState contains the latest state of a signal connection
type SignalState struct {
URL string
@@ -501,7 +507,7 @@ func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
func (d *Status) GetLocalPeerState() LocalPeerState {
d.mux.Lock()
defer d.mux.Unlock()
return d.localPeer
return d.localPeer.Clone()
}
// UpdateLocalPeerState updates local peer status

View File

@@ -266,7 +266,7 @@ func addRoute(prefix netip.Prefix, nexthop Nexthop, tableID int) error {
return fmt.Errorf("add gateway and device: %w", err)
}
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.EAFNOSUPPORT) {
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) && !isOpErr(err) {
return fmt.Errorf("netlink add route: %w", err)
}
@@ -289,7 +289,7 @@ func addUnreachableRoute(prefix netip.Prefix, tableID int) error {
Dst: ipNet,
}
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.EAFNOSUPPORT) {
if err := netlink.RouteAdd(route); err != nil && !errors.Is(err, syscall.EEXIST) && !isOpErr(err) {
return fmt.Errorf("netlink add unreachable route: %w", err)
}
@@ -312,7 +312,7 @@ func removeUnreachableRoute(prefix netip.Prefix, tableID int) error {
if err := netlink.RouteDel(route); err != nil &&
!errors.Is(err, syscall.ESRCH) &&
!errors.Is(err, syscall.ENOENT) &&
!errors.Is(err, syscall.EAFNOSUPPORT) {
!isOpErr(err) {
return fmt.Errorf("netlink remove unreachable route: %w", err)
}
@@ -338,7 +338,7 @@ func removeRoute(prefix netip.Prefix, nexthop Nexthop, tableID int) error {
return fmt.Errorf("add gateway and device: %w", err)
}
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) && !errors.Is(err, syscall.EAFNOSUPPORT) {
if err := netlink.RouteDel(route); err != nil && !errors.Is(err, syscall.ESRCH) && !isOpErr(err) {
return fmt.Errorf("netlink remove route: %w", err)
}
@@ -362,7 +362,7 @@ func flushRoutes(tableID, family int) error {
routes[i].Dst = &net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)}
}
}
if err := netlink.RouteDel(&routes[i]); err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
if err := netlink.RouteDel(&routes[i]); err != nil && !isOpErr(err) {
result = multierror.Append(result, fmt.Errorf("failed to delete route %v from table %d: %w", routes[i], tableID, err))
}
}
@@ -450,7 +450,7 @@ func addRule(params ruleParams) error {
rule.Invert = params.invert
rule.SuppressPrefixlen = params.suppressPrefix
if err := netlink.RuleAdd(rule); err != nil && !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.EAFNOSUPPORT) {
if err := netlink.RuleAdd(rule); err != nil && !errors.Is(err, syscall.EEXIST) && !isOpErr(err) {
return fmt.Errorf("add routing rule: %w", err)
}
@@ -467,7 +467,7 @@ func removeRule(params ruleParams) error {
rule.Priority = params.priority
rule.SuppressPrefixlen = params.suppressPrefix
if err := netlink.RuleDel(rule); err != nil && !errors.Is(err, syscall.ENOENT) && !errors.Is(err, syscall.EAFNOSUPPORT) {
if err := netlink.RuleDel(rule); err != nil && !errors.Is(err, syscall.ENOENT) && !isOpErr(err) {
return fmt.Errorf("remove routing rule: %w", err)
}
@@ -509,3 +509,13 @@ func hasSeparateRouting() ([]netip.Prefix, error) {
}
return nil, ErrRoutingIsSeparate
}
func isOpErr(err error) bool {
// EAFTNOSUPPORT when ipv6 is disabled via sysctl, EOPNOTSUPP when disabled in boot options or otherwise not supported
if errors.Is(err, syscall.EAFNOSUPPORT) || errors.Is(err, syscall.EOPNOTSUPP) {
log.Debugf("route operation not supported: %v", err)
return true
}
return false
}

View File

@@ -1,23 +1,16 @@
package statemanager
import (
"github.com/netbirdio/netbird/client/configs"
"os"
"path/filepath"
"runtime"
)
// GetDefaultStatePath returns the path to the state file based on the operating system
// It returns an empty string if the path cannot be determined.
func GetDefaultStatePath() string {
switch runtime.GOOS {
case "windows":
return filepath.Join(os.Getenv("PROGRAMDATA"), "Netbird", "state.json")
case "darwin", "linux":
return "/var/lib/netbird/state.json"
case "freebsd", "openbsd", "netbsd", "dragonfly":
return "/var/db/netbird/state.json"
if path := os.Getenv("NB_DNS_STATE_FILE"); path != "" {
return path
}
return ""
return filepath.Join(configs.StateDir, "state.json")
}