mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 00:06:38 +00:00
Release 0.28.0 (#2092)
* compile client under freebsd (#1620) Compile netbird client under freebsd and now support netstack and userspace modes. Refactoring linux specific code to share same code with FreeBSD, move to *_unix.go files. Not implemented yet: Kernel mode not supported DNS probably does not work yet Routing also probably does not work yet SSH support did not tested yet Lack of test environment for freebsd (dedicated VM for github runners under FreeBSD required) Lack of tests for freebsd specific code info reporting need to review and also implement, for example OS reported as GENERIC instead of FreeBSD (lack of FreeBSD icon in management interface) Lack of proper client setup under FreeBSD Lack of FreeBSD port/package * Add DNS routes (#1943) Given domains are resolved periodically and resolved IPs are replaced with the new ones. Unless the flag keep_route is set to true, then only new ones are added. This option is helpful if there are long-running connections that might still point to old IP addresses from changed DNS records. * Add process posture check (#1693) Introduces a process posture check to validate the existence and active status of specific binaries on peer systems. The check ensures that files are present at specified paths, and that corresponding processes are running. This check supports Linux, Windows, and macOS systems. Co-authored-by: Evgenii <mail@skillcoder.com> Co-authored-by: Pascal Fischer <pascal@netbird.io> Co-authored-by: Zoltan Papp <zoltan.pmail@gmail.com> Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com> Co-authored-by: Bethuel Mmbaga <bethuelmbaga12@gmail.com>
This commit is contained in:
@@ -23,24 +23,6 @@ func parseWGAddress(address string) (WGAddress, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Masked returns the WGAddress with the IP address part masked according to its network mask.
|
||||
func (addr WGAddress) Masked() WGAddress {
|
||||
ip := addr.IP.To4()
|
||||
if ip == nil {
|
||||
ip = addr.IP.To16()
|
||||
}
|
||||
|
||||
maskedIP := make(net.IP, len(ip))
|
||||
for i := range ip {
|
||||
maskedIP[i] = ip[i] & addr.Network.Mask[i]
|
||||
}
|
||||
|
||||
return WGAddress{
|
||||
IP: maskedIP,
|
||||
Network: addr.Network,
|
||||
}
|
||||
}
|
||||
|
||||
func (addr WGAddress) String() string {
|
||||
maskSize, _ := addr.Network.Mask.Size()
|
||||
return fmt.Sprintf("%s/%d", addr.IP.String(), maskSize)
|
||||
|
||||
8
iface/freebsd/errors.go
Normal file
8
iface/freebsd/errors.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package freebsd
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrDoesNotExist = errors.New("does not exist")
|
||||
ErrNameDoesNotMatch = errors.New("name does not match")
|
||||
)
|
||||
108
iface/freebsd/iface.go
Normal file
108
iface/freebsd/iface.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package freebsd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type iface struct {
|
||||
Name string
|
||||
MTU int
|
||||
Group string
|
||||
IPAddrs []string
|
||||
}
|
||||
|
||||
func parseError(output []byte) error {
|
||||
// TODO: implement without allocations
|
||||
lines := string(output)
|
||||
|
||||
if strings.Contains(lines, "does not exist") {
|
||||
return ErrDoesNotExist
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseIfconfigOutput(output []byte) (*iface, error) {
|
||||
// TODO: implement without allocations
|
||||
lines := string(output)
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(lines))
|
||||
|
||||
var name, mtu, group string
|
||||
var ips []string
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// If line contains ": flags", it's a line with interface information
|
||||
if strings.Contains(line, ": flags") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 4 {
|
||||
return nil, fmt.Errorf("failed to parse line: %s", line)
|
||||
}
|
||||
name = strings.TrimSuffix(parts[0], ":")
|
||||
if strings.Contains(line, "mtu") {
|
||||
mtuIndex := 0
|
||||
for i, part := range parts {
|
||||
if part == "mtu" {
|
||||
mtuIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
mtu = parts[mtuIndex+1]
|
||||
}
|
||||
}
|
||||
|
||||
// If line contains "groups:", it's a line with interface group
|
||||
if strings.Contains(line, "groups:") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("failed to parse line: %s", line)
|
||||
}
|
||||
group = parts[1]
|
||||
}
|
||||
|
||||
// If line contains "inet ", it's a line with IP address
|
||||
if strings.Contains(line, "inet ") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("failed to parse line: %s", line)
|
||||
}
|
||||
ips = append(ips, parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("interface name not found in ifconfig output")
|
||||
}
|
||||
|
||||
mtuInt, err := strconv.Atoi(mtu)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse MTU: %w", err)
|
||||
}
|
||||
|
||||
return &iface{
|
||||
Name: name,
|
||||
MTU: mtuInt,
|
||||
Group: group,
|
||||
IPAddrs: ips,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseIFName(output []byte) (string, error) {
|
||||
// TODO: implement without allocations
|
||||
lines := strings.Split(string(output), "\n")
|
||||
if len(lines) == 0 || lines[0] == "" {
|
||||
return "", fmt.Errorf("no output returned")
|
||||
}
|
||||
|
||||
fields := strings.Fields(lines[0])
|
||||
if len(fields) > 1 {
|
||||
return "", fmt.Errorf("invalid output")
|
||||
}
|
||||
|
||||
return fields[0], nil
|
||||
}
|
||||
76
iface/freebsd/iface_internal_test.go
Normal file
76
iface/freebsd/iface_internal_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package freebsd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseIfconfigOutput(t *testing.T) {
|
||||
testOutput := `wg1: flags=8080<NOARP,MULTICAST> metric 0 mtu 1420
|
||||
options=80000<LINKSTATE>
|
||||
groups: wg
|
||||
nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>`
|
||||
|
||||
expected := &iface{
|
||||
Name: "wg1",
|
||||
MTU: 1420,
|
||||
Group: "wg",
|
||||
}
|
||||
|
||||
result, err := parseIfconfigOutput(([]byte)(testOutput))
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing ifconfig output: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.Name, result.Name, "Name should match")
|
||||
assert.Equal(t, expected.MTU, result.MTU, "MTU should match")
|
||||
assert.Equal(t, expected.Group, result.Group, "Group should match")
|
||||
}
|
||||
|
||||
func TestParseIFName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
output string
|
||||
expected string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "ValidOutput",
|
||||
output: "eth0\n",
|
||||
expected: "eth0",
|
||||
},
|
||||
{
|
||||
name: "ValidOutputOneLine",
|
||||
output: "eth0",
|
||||
expected: "eth0",
|
||||
},
|
||||
{
|
||||
name: "EmptyOutput",
|
||||
output: "",
|
||||
expectedErr: fmt.Errorf("no output returned"),
|
||||
},
|
||||
{
|
||||
name: "InvalidOutput",
|
||||
output: "This is an invalid output\n",
|
||||
expectedErr: fmt.Errorf("invalid output"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := parseIFName(([]byte)(test.output))
|
||||
|
||||
assert.Equal(t, test.expected, result, "Interface names should match")
|
||||
|
||||
if test.expectedErr != nil {
|
||||
assert.NotNil(t, err, "Error should not be nil")
|
||||
assert.EqualError(t, err, test.expectedErr.Error(), "Error messages should match")
|
||||
} else {
|
||||
assert.Nil(t, err, "Error should be nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
239
iface/freebsd/link.go
Normal file
239
iface/freebsd/link.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package freebsd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const wgIFGroup = "wg"
|
||||
|
||||
// Link represents a network interface.
|
||||
type Link struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func NewLink(name string) *Link {
|
||||
return &Link{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// LinkByName retrieves a network interface by its name.
|
||||
func LinkByName(name string) (*Link, error) {
|
||||
out, err := exec.Command("ifconfig", name).CombinedOutput()
|
||||
if err != nil {
|
||||
if pErr := parseError(out); pErr != nil {
|
||||
return nil, pErr
|
||||
}
|
||||
|
||||
log.Debugf("ifconfig out: %s", out)
|
||||
|
||||
return nil, fmt.Errorf("command run: %w", err)
|
||||
}
|
||||
|
||||
i, err := parseIfconfigOutput(out)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse ifconfig output: %w", err)
|
||||
}
|
||||
|
||||
if i.Name != name {
|
||||
return nil, ErrNameDoesNotMatch
|
||||
}
|
||||
|
||||
return &Link{name: i.Name}, nil
|
||||
}
|
||||
|
||||
// Recreate - create new interface, remove current before create if it exists
|
||||
func (l *Link) Recreate() error {
|
||||
ok, err := l.isExist()
|
||||
if err != nil {
|
||||
return fmt.Errorf("is exist: %w", err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
if err := l.del(l.name); err != nil {
|
||||
return fmt.Errorf("del: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return l.Add()
|
||||
}
|
||||
|
||||
// Add creates a new network interface.
|
||||
func (l *Link) Add() error {
|
||||
parsedName, err := l.create(wgIFGroup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create link: %w", err)
|
||||
}
|
||||
|
||||
if parsedName == l.name {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedName, err = l.rename(parsedName, l.name)
|
||||
if err != nil {
|
||||
errDel := l.del(parsedName)
|
||||
if errDel != nil {
|
||||
return fmt.Errorf("del on rename link: %w: %w", err, errDel)
|
||||
}
|
||||
|
||||
return fmt.Errorf("rename link: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Del removes an existing network interface.
|
||||
func (l *Link) Del() error {
|
||||
return l.del(l.name)
|
||||
}
|
||||
|
||||
// SetMTU sets the MTU of the network interface.
|
||||
func (l *Link) SetMTU(mtu int) error {
|
||||
return l.setMTU(mtu)
|
||||
}
|
||||
|
||||
// AssignAddr assigns an IP address and netmask to the network interface.
|
||||
func (l *Link) AssignAddr(ip, netmask string) error {
|
||||
return l.setAddr(ip, netmask)
|
||||
}
|
||||
|
||||
func (l *Link) Up() error {
|
||||
return l.up(l.name)
|
||||
}
|
||||
|
||||
func (l *Link) Down() error {
|
||||
return l.down(l.name)
|
||||
}
|
||||
|
||||
func (l *Link) isExist() (bool, error) {
|
||||
_, err := LinkByName(l.name)
|
||||
if errors.Is(err, ErrDoesNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("link by name: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (l *Link) create(groupName string) (string, error) {
|
||||
cmd := exec.Command("ifconfig", groupName, "create")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", output)
|
||||
|
||||
return "", fmt.Errorf("create %s interface: %w", groupName, err)
|
||||
}
|
||||
|
||||
interfaceName, err := parseIFName(output)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse interface name: %w", err)
|
||||
}
|
||||
|
||||
return interfaceName, nil
|
||||
}
|
||||
|
||||
func (l *Link) rename(oldName, newName string) (string, error) {
|
||||
cmd := exec.Command("ifconfig", oldName, "name", newName)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", output)
|
||||
|
||||
return "", fmt.Errorf("change name %q -> %q: %w", oldName, newName, err)
|
||||
}
|
||||
|
||||
interfaceName, err := parseIFName(output)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse new name: %w", err)
|
||||
}
|
||||
|
||||
return interfaceName, nil
|
||||
}
|
||||
|
||||
func (l *Link) del(name string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", name, "destroy")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("destroy %s interface: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) setMTU(mtu int) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", l.name, "mtu", strconv.Itoa(mtu))
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("set interface mtu: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) setAddr(ip, netmask string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", l.name, "inet", ip, "netmask", netmask)
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("set interface addr: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) up(name string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", name, "up")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("up %s interface: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) down(name string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", name, "down")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("down %s interface: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -48,6 +48,19 @@ func (w *WGIface) Address() WGAddress {
|
||||
return w.tun.WgAddress()
|
||||
}
|
||||
|
||||
// ToInterface returns the net.Interface for the Wireguard interface
|
||||
func (r *WGIface) ToInterface() *net.Interface {
|
||||
name := r.tun.DeviceName()
|
||||
intf, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get interface by name %s: %v", name, err)
|
||||
intf = &net.Interface{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
return intf
|
||||
}
|
||||
|
||||
// Up configures a Wireguard interface
|
||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||
func (w *WGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
||||
@@ -94,7 +107,7 @@ func (w *WGIface) AddAllowedIP(peerKey string, allowedIP string) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
log.Debugf("adding allowed IP to interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
|
||||
log.Debugf("Adding allowed IP to interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
|
||||
return w.configurer.addAllowedIP(peerKey, allowedIP)
|
||||
}
|
||||
|
||||
@@ -103,7 +116,7 @@ func (w *WGIface) RemoveAllowedIP(peerKey string, allowedIP string) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
log.Debugf("removing allowed IP from interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
|
||||
log.Debugf("Removing allowed IP from interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
|
||||
return w.configurer.removeAllowedIP(peerKey, allowedIP)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
package iface
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !ios
|
||||
// +build !ios
|
||||
|
||||
package iface
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build ios
|
||||
// +build ios
|
||||
|
||||
package iface
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:build !android
|
||||
// +build !android
|
||||
//go:build (linux && !android) || freebsd
|
||||
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/pion/transport/v3"
|
||||
|
||||
@@ -43,5 +43,5 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
||||
|
||||
// CreateOnAndroid this function make sense on mobile only
|
||||
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
||||
return fmt.Errorf("this function has not implemented on this platform")
|
||||
return fmt.Errorf("CreateOnAndroid function has not implemented on %s platform", runtime.GOOS)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !linux || android
|
||||
// +build !linux android
|
||||
//go:build (!linux && !freebsd) || android
|
||||
|
||||
package iface
|
||||
|
||||
|
||||
18
iface/module_freebsd.go
Normal file
18
iface/module_freebsd.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package iface
|
||||
|
||||
// WireGuardModuleIsLoaded check if kernel support wireguard
|
||||
func WireGuardModuleIsLoaded() bool {
|
||||
// Despite the fact FreeBSD natively support Wireguard (https://github.com/WireGuard/wireguard-freebsd)
|
||||
// we are currently do not use it, since it is required to add wireguard kernel support to
|
||||
// - https://github.com/netbirdio/netbird/tree/main/sharedsock
|
||||
// - https://github.com/mdlayher/socket
|
||||
// TODO: implement kernel space
|
||||
return false
|
||||
}
|
||||
|
||||
// tunModuleIsLoaded check if tun module exist, if is not attempt to load it
|
||||
func tunModuleIsLoaded() bool {
|
||||
// Assume tun supported by freebsd kernel by default
|
||||
// TODO: implement check for module loaded in kernel or build-it
|
||||
return true
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || windows
|
||||
// +build linux windows
|
||||
//go:build linux || windows || freebsd
|
||||
|
||||
package iface
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package iface
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build linux && !android
|
||||
//go:build (linux && !android) || freebsd
|
||||
|
||||
package iface
|
||||
|
||||
@@ -6,11 +6,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/pion/transport/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/netbirdio/netbird/iface/bind"
|
||||
"github.com/netbirdio/netbird/sharedsock"
|
||||
@@ -32,6 +30,8 @@ type tunKernelDevice struct {
|
||||
}
|
||||
|
||||
func newTunDevice(name string, address WGAddress, wgPort int, key string, mtu int, transportNet transport.Net) wgTunDevice {
|
||||
checkUser()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &tunKernelDevice{
|
||||
ctx: ctx,
|
||||
@@ -48,53 +48,29 @@ func newTunDevice(name string, address WGAddress, wgPort int, key string, mtu in
|
||||
func (t *tunKernelDevice) Create() (wgConfigurer, error) {
|
||||
link := newWGLink(t.name)
|
||||
|
||||
// check if interface exists
|
||||
l, err := netlink.LinkByName(t.name)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case netlink.LinkNotFoundError:
|
||||
break
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// remove if interface exists
|
||||
if l != nil {
|
||||
err = netlink.LinkDel(link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding device: %s", t.name)
|
||||
err = netlink.LinkAdd(link)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already exists. Will reuse.", t.name)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
if err := link.recreate(); err != nil {
|
||||
return nil, fmt.Errorf("recreate: %w", err)
|
||||
}
|
||||
|
||||
t.link = link
|
||||
|
||||
err = t.assignAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err := t.assignAddr(); err != nil {
|
||||
return nil, fmt.Errorf("assign addr: %w", err)
|
||||
}
|
||||
|
||||
// todo do a discovery
|
||||
// TODO: do a MTU discovery
|
||||
log.Debugf("setting MTU: %d interface: %s", t.mtu, t.name)
|
||||
err = netlink.LinkSetMTU(link, t.mtu)
|
||||
if err != nil {
|
||||
log.Errorf("error setting MTU on interface: %s", t.name)
|
||||
return nil, err
|
||||
|
||||
if err := link.setMTU(t.mtu); err != nil {
|
||||
return nil, fmt.Errorf("set mtu: %w", err)
|
||||
}
|
||||
|
||||
configurer := newWGConfigurer(t.name)
|
||||
err = configurer.configureInterface(t.key, t.wgPort)
|
||||
if err != nil {
|
||||
|
||||
if err := configurer.configureInterface(t.key, t.wgPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return configurer, nil
|
||||
}
|
||||
|
||||
@@ -108,9 +84,10 @@ func (t *tunKernelDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
||||
}
|
||||
|
||||
log.Debugf("bringing up interface: %s", t.name)
|
||||
err := netlink.LinkSetUp(t.link)
|
||||
if err != nil {
|
||||
|
||||
if err := t.link.up(); err != nil {
|
||||
log.Errorf("error bringing up interface: %s", t.name)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -178,32 +155,5 @@ func (t *tunKernelDevice) Wrapper() *DeviceWrapper {
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface
|
||||
func (t *tunKernelDevice) assignAddr() error {
|
||||
link := newWGLink(t.name)
|
||||
|
||||
//delete existing addresses
|
||||
list, err := netlink.AddrList(link, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
for _, a := range list {
|
||||
addr := a
|
||||
err = netlink.AddrDel(link, &addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", t.address.String(), t.name)
|
||||
addr, _ := netlink.ParseAddr(t.address.String())
|
||||
err = netlink.AddrAdd(link, addr)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", t.name, t.address.String())
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
// On linux, the link must be brought up
|
||||
err = netlink.LinkSetUp(link)
|
||||
return err
|
||||
return t.link.assignAddr(t.address)
|
||||
}
|
||||
80
iface/tun_link_freebsd.go
Normal file
80
iface/tun_link_freebsd.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/netbirdio/netbird/iface/freebsd"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type wgLink struct {
|
||||
name string
|
||||
link *freebsd.Link
|
||||
}
|
||||
|
||||
func newWGLink(name string) *wgLink {
|
||||
link := freebsd.NewLink(name)
|
||||
|
||||
return &wgLink{
|
||||
name: name,
|
||||
link: link,
|
||||
}
|
||||
}
|
||||
|
||||
// Type returns the interface type
|
||||
func (l *wgLink) Type() string {
|
||||
return "wireguard"
|
||||
}
|
||||
|
||||
// Close deletes the link interface
|
||||
func (l *wgLink) Close() error {
|
||||
return l.link.Del()
|
||||
}
|
||||
|
||||
func (l *wgLink) recreate() error {
|
||||
if err := l.link.Recreate(); err != nil {
|
||||
return fmt.Errorf("recreate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *wgLink) setMTU(mtu int) error {
|
||||
if err := l.link.SetMTU(mtu); err != nil {
|
||||
return fmt.Errorf("set mtu: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *wgLink) up() error {
|
||||
if err := l.link.Up(); err != nil {
|
||||
return fmt.Errorf("up: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *wgLink) assignAddr(address WGAddress) error {
|
||||
link, err := freebsd.LinkByName(l.name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("link by name: %w", err)
|
||||
}
|
||||
|
||||
ip := address.IP.String()
|
||||
mask := "0x" + address.Network.Mask.String()
|
||||
|
||||
log.Infof("assign addr %s mask %s to %s interface", ip, mask, l.name)
|
||||
|
||||
err = link.AssignAddr(ip, mask)
|
||||
if err != nil {
|
||||
return fmt.Errorf("assign addr: %w", err)
|
||||
}
|
||||
|
||||
err = link.Up()
|
||||
if err != nil {
|
||||
return fmt.Errorf("up: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
package iface
|
||||
|
||||
import "github.com/vishvananda/netlink"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
type wgLink struct {
|
||||
attrs *netlink.LinkAttrs
|
||||
@@ -31,3 +37,97 @@ func (l *wgLink) Type() string {
|
||||
func (l *wgLink) Close() error {
|
||||
return netlink.LinkDel(l)
|
||||
}
|
||||
|
||||
func (l *wgLink) recreate() error {
|
||||
name := l.attrs.Name
|
||||
|
||||
// check if interface exists
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case netlink.LinkNotFoundError:
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("link by name: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// remove if interface exists
|
||||
if link != nil {
|
||||
err = netlink.LinkDel(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding device: %s", name)
|
||||
err = netlink.LinkAdd(l)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already exists. Will reuse.", name)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("link add: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *wgLink) setMTU(mtu int) error {
|
||||
if err := netlink.LinkSetMTU(l, mtu); err != nil {
|
||||
log.Errorf("error setting MTU on interface: %s", l.attrs.Name)
|
||||
|
||||
return fmt.Errorf("link set mtu: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *wgLink) up() error {
|
||||
if err := netlink.LinkSetUp(l); err != nil {
|
||||
log.Errorf("error bringing up interface: %s", l.attrs.Name)
|
||||
return fmt.Errorf("link setup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *wgLink) assignAddr(address WGAddress) error {
|
||||
//delete existing addresses
|
||||
list, err := netlink.AddrList(l, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list addr: %w", err)
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
for _, a := range list {
|
||||
addr := a
|
||||
err = netlink.AddrDel(l, &addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("del addr: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name := l.attrs.Name
|
||||
addrStr := address.String()
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", addrStr, name)
|
||||
|
||||
addr, err := netlink.ParseAddr(addrStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse addr: %w", err)
|
||||
}
|
||||
|
||||
err = netlink.AddrAdd(l, addr)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", name, addrStr)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("add addr: %w", err)
|
||||
}
|
||||
|
||||
// On linux, the link must be brought up
|
||||
if err := netlink.LinkSetUp(l); err != nil {
|
||||
return fmt.Errorf("link setup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//go:build linux && !android
|
||||
//go:build (linux && !android) || freebsd
|
||||
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/pion/transport/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
@@ -31,6 +31,9 @@ type tunUSPDevice struct {
|
||||
|
||||
func newTunUSPDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net) wgTunDevice {
|
||||
log.Infof("using userspace bind mode")
|
||||
|
||||
checkUser()
|
||||
|
||||
return &tunUSPDevice{
|
||||
name: name,
|
||||
address: address,
|
||||
@@ -129,30 +132,14 @@ func (t *tunUSPDevice) Wrapper() *DeviceWrapper {
|
||||
func (t *tunUSPDevice) assignAddr() error {
|
||||
link := newWGLink(t.name)
|
||||
|
||||
//delete existing addresses
|
||||
list, err := netlink.AddrList(link, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
for _, a := range list {
|
||||
addr := a
|
||||
err = netlink.AddrDel(link, &addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return link.assignAddr(t.address)
|
||||
}
|
||||
|
||||
func checkUser() {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
euid := os.Geteuid()
|
||||
if euid != 0 {
|
||||
log.Warn("newTunUSPDevice: on netbird must run as root to be able to assign address to the tun interface with ifconfig")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", t.address.String(), t.name)
|
||||
addr, _ := netlink.ParseAddr(t.address.String())
|
||||
err = netlink.AddrAdd(link, addr)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", t.name, t.address.String())
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
// On linux, the link must be brought up
|
||||
err = netlink.LinkSetUp(link)
|
||||
return err
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
var ErrPeerNotFound = errors.New("peer not found")
|
||||
|
||||
type wgConfigurer interface {
|
||||
configureInterface(privateKey string, port int) error
|
||||
updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build linux && !android
|
||||
//go:build (linux && !android) || freebsd
|
||||
|
||||
package iface
|
||||
|
||||
@@ -125,17 +125,17 @@ func (c *wgKernelConfigurer) addAllowedIP(peerKey string, allowedIP string) erro
|
||||
func (c *wgKernelConfigurer) removeAllowedIP(peerKey string, allowedIP string) error {
|
||||
_, ipNet, err := net.ParseCIDR(allowedIP)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("parse allowed IP: %w", err)
|
||||
}
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("parse peer key: %w", err)
|
||||
}
|
||||
|
||||
existingPeer, err := c.getPeer(c.deviceName, peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("get peer: %w", err)
|
||||
}
|
||||
|
||||
newAllowedIPs := existingPeer.AllowedIPs
|
||||
@@ -159,7 +159,7 @@ func (c *wgKernelConfigurer) removeAllowedIP(peerKey string, allowedIP string) e
|
||||
}
|
||||
err = c.configure(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`received error "%w" while removing allowed IP from peer on interface %s with settings: allowed ips %s`, err, c.deviceName, allowedIP)
|
||||
return fmt.Errorf("remove allowed IP %s on interface %s: %w", allowedIP, c.deviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -167,25 +167,25 @@ func (c *wgKernelConfigurer) removeAllowedIP(peerKey string, allowedIP string) e
|
||||
func (c *wgKernelConfigurer) getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) {
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return wgtypes.Peer{}, err
|
||||
return wgtypes.Peer{}, fmt.Errorf("wgctl: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
err = wg.Close()
|
||||
if err != nil {
|
||||
log.Errorf("got error while closing wgctl: %v", err)
|
||||
log.Errorf("Got error while closing wgctl: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
wgDevice, err := wg.Device(ifaceName)
|
||||
if err != nil {
|
||||
return wgtypes.Peer{}, err
|
||||
return wgtypes.Peer{}, fmt.Errorf("get device %s: %w", ifaceName, err)
|
||||
}
|
||||
for _, peer := range wgDevice.Peers {
|
||||
if peer.PublicKey.String() == peerPubKey {
|
||||
return peer, nil
|
||||
}
|
||||
}
|
||||
return wgtypes.Peer{}, fmt.Errorf("peer not found")
|
||||
return wgtypes.Peer{}, ErrPeerNotFound
|
||||
}
|
||||
|
||||
func (c *wgKernelConfigurer) configure(config wgtypes.Config) error {
|
||||
@@ -200,7 +200,6 @@ func (c *wgKernelConfigurer) configure(config wgtypes.Config) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Tracef("got Wireguard device %s", c.deviceName)
|
||||
|
||||
return wg.ConfigureDevice(c.deviceName, config)
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
nbnet "github.com/netbirdio/netbird/util/net"
|
||||
)
|
||||
|
||||
var ErrAllowedIPNotFound = fmt.Errorf("allowed IP not found")
|
||||
|
||||
type wgUSPConfigurer struct {
|
||||
device *device.Device
|
||||
deviceName string
|
||||
@@ -173,7 +175,7 @@ func (c *wgUSPConfigurer) removeAllowedIP(peerKey string, ip string) error {
|
||||
}
|
||||
|
||||
if !removedAllowedIP {
|
||||
return fmt.Errorf("allowedIP not found")
|
||||
return ErrAllowedIPNotFound
|
||||
}
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
@@ -301,7 +303,7 @@ func findPeerInfo(ipcInput string, peerKey string, searchConfigKeys []string) (m
|
||||
}
|
||||
}
|
||||
if !foundPeer {
|
||||
return nil, fmt.Errorf("peer not found: %s", peerKey)
|
||||
return nil, fmt.Errorf("%w: %s", ErrPeerNotFound, peerKey)
|
||||
}
|
||||
|
||||
return configFound, nil
|
||||
|
||||
Reference in New Issue
Block a user