Skip to content

Commit

Permalink
Merge branch 'main' into userspace-router
Browse files Browse the repository at this point in the history
  • Loading branch information
lixmal committed Jan 14, 2025
2 parents 1c00870 + 9b5b632 commit a625f90
Show file tree
Hide file tree
Showing 20 changed files with 280 additions and 93 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/golang-test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:
jobs:
build-cache:
runs-on: ubuntu-22.04
steps:
steps:
- name: Checkout code
uses: actions/checkout@v4

Expand Down Expand Up @@ -183,7 +183,7 @@ jobs:
run: git --no-pager diff --exit-code

- name: Login to Docker hub
if: matrix.store == 'mysql'
if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref)
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
Expand Down Expand Up @@ -243,7 +243,7 @@ jobs:
run: git --no-pager diff --exit-code

- name: Login to Docker hub
if: matrix.store == 'mysql'
if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref)
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
Expand Down Expand Up @@ -303,7 +303,7 @@ jobs:
run: git --no-pager diff --exit-code

- name: Login to Docker hub
if: matrix.store == 'mysql'
if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref)
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
Expand Down
24 changes: 24 additions & 0 deletions client/configs/configs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package configs

import (
"os"
"path/filepath"
"runtime"
)

var StateDir string

func init() {
StateDir = os.Getenv("NB_STATE_DIR")
if StateDir != "" {
return
}
switch runtime.GOOS {
case "windows":
StateDir = filepath.Join(os.Getenv("PROGRAMDATA"), "Netbird")
case "darwin", "linux":
StateDir = "/var/lib/netbird"
case "freebsd", "openbsd", "netbsd", "dragonfly":
StateDir = "/var/db/netbird"
}
}
18 changes: 18 additions & 0 deletions client/internal/dns/consts.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
5 changes: 0 additions & 5 deletions client/internal/dns/consts_freebsd.go

This file was deleted.

7 changes: 0 additions & 7 deletions client/internal/dns/consts_linux.go

This file was deleted.

72 changes: 66 additions & 6 deletions client/internal/dns/resolvconf_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net/netip"
"os/exec"
"strings"

log "github.com/sirupsen/logrus"

Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
13 changes: 7 additions & 6 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
8 changes: 7 additions & 1 deletion client/internal/peer/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
24 changes: 17 additions & 7 deletions client/internal/routemanager/systemops/systemops_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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))
}
}
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand Down Expand Up @@ -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
}
15 changes: 4 additions & 11 deletions client/internal/statemanager/path.go
Original file line number Diff line number Diff line change
@@ -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")
}
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ require (
)

require (
fyne.io/fyne/v2 v2.5.0
fyne.io/fyne/v2 v2.5.3
fyne.io/systray v1.11.0
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible
github.com/c-robinson/iplib v1.0.3
Expand Down Expand Up @@ -147,7 +147,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect
github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
Expand All @@ -156,8 +156,8 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/go-text/render v0.1.0 // indirect
github.com/go-text/typesetting v0.1.0 // indirect
github.com/go-text/render v0.2.0 // indirect
github.com/go-text/typesetting v0.2.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
Expand Down Expand Up @@ -207,7 +207,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/rymdport/portal v0.2.2 // indirect
github.com/rymdport/portal v0.3.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
Expand Down
Loading

0 comments on commit a625f90

Please sign in to comment.