mirror of
https://github.com/slackhq/nebula.git
synced 2025-11-22 08:24:25 +01:00
276 lines
6.5 KiB
Go
276 lines
6.5 KiB
Go
//go:build freebsd && !e2e_testing
|
|
// +build freebsd,!e2e_testing
|
|
|
|
package overlay
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/netip"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/slackhq/nebula/config"
|
|
"github.com/slackhq/nebula/util"
|
|
"golang.org/x/sys/unix"
|
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
|
)
|
|
|
|
type tun struct{}
|
|
|
|
// ifreqRename is used for renaming network interfaces on FreeBSD
|
|
type ifreqRename struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Data uintptr
|
|
}
|
|
|
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) {
|
|
return nil, fmt.Errorf("newTunFromFd not supported on FreeBSD")
|
|
}
|
|
|
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) {
|
|
deviceName := c.GetString("tun.dev", "tun")
|
|
mtu := c.GetInt("tun.mtu", DefaultMTU)
|
|
|
|
// Create WireGuard TUN device
|
|
tunDevice, err := wgtun.CreateTUN(deviceName, mtu)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create TUN device: %w", err)
|
|
}
|
|
|
|
// Get the actual device name
|
|
actualName, err := tunDevice.Name()
|
|
if err != nil {
|
|
tunDevice.Close()
|
|
return nil, fmt.Errorf("failed to get TUN device name: %w", err)
|
|
}
|
|
|
|
// If the name doesn't match the desired interface name, rename it now
|
|
if actualName != deviceName && deviceName != "" && deviceName != "tun" {
|
|
if err := renameInterface(actualName, deviceName); err != nil {
|
|
tunDevice.Close()
|
|
return nil, fmt.Errorf("failed to rename interface from %s to %s: %w", actualName, deviceName, err)
|
|
}
|
|
actualName = deviceName
|
|
}
|
|
|
|
t := &wgTun{
|
|
tunDevice: tunDevice,
|
|
vpnNetworks: vpnNetworks,
|
|
MaxMTU: mtu,
|
|
DefaultMTU: mtu,
|
|
l: l,
|
|
}
|
|
|
|
// Create FreeBSD-specific route manager
|
|
t.routeManager = &tun{}
|
|
|
|
err = t.reload(c, true)
|
|
if err != nil {
|
|
tunDevice.Close()
|
|
return nil, err
|
|
}
|
|
|
|
c.RegisterReloadCallback(func(c *config.C) {
|
|
err := t.reload(c, false)
|
|
if err != nil {
|
|
util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
|
|
}
|
|
})
|
|
|
|
l.WithField("name", actualName).Info("Created WireGuard TUN device")
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (rm *tun) Activate(t *wgTun) error {
|
|
name, err := t.tunDevice.Name()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get device name: %w", err)
|
|
}
|
|
|
|
// Set the MTU
|
|
rm.SetMTU(t, t.MaxMTU)
|
|
|
|
// Add IP addresses
|
|
for _, network := range t.vpnNetworks {
|
|
if err := rm.addIP(t, name, network); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Bring up the interface
|
|
if err := runCommandBSD("ifconfig", name, "up"); err != nil {
|
|
return fmt.Errorf("failed to bring up interface: %w", err)
|
|
}
|
|
|
|
// Set the routes
|
|
if err := rm.AddRoutes(t, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rm *tun) SetMTU(t *wgTun, mtu int) {
|
|
name, err := t.tunDevice.Name()
|
|
if err != nil {
|
|
t.l.WithError(err).Error("Failed to get device name for MTU set")
|
|
return
|
|
}
|
|
|
|
if err := runCommandBSD("ifconfig", name, "mtu", strconv.Itoa(mtu)); err != nil {
|
|
t.l.WithError(err).Error("Failed to set tun mtu")
|
|
}
|
|
}
|
|
|
|
func (rm *tun) SetDefaultRoute(t *wgTun, cidr netip.Prefix) error {
|
|
// On FreeBSD, routes are set via ifconfig and route commands
|
|
return nil
|
|
}
|
|
|
|
func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error {
|
|
name, err := t.tunDevice.Name()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get device name: %w", err)
|
|
}
|
|
|
|
routes := *t.Routes.Load()
|
|
for _, r := range routes {
|
|
if !r.Install {
|
|
continue
|
|
}
|
|
|
|
// Add route using route command
|
|
args := []string{"add"}
|
|
|
|
if r.Cidr.Addr().Is6() {
|
|
args = append(args, "-inet6")
|
|
} else {
|
|
args = append(args, "-inet")
|
|
}
|
|
|
|
args = append(args, r.Cidr.String(), "-interface", name)
|
|
|
|
if r.Metric > 0 {
|
|
// FreeBSD doesn't support route metrics directly like Linux
|
|
t.l.WithField("route", r).Warn("Route metrics are not fully supported on FreeBSD")
|
|
}
|
|
|
|
err := runCommandBSD("route", args...)
|
|
if err != nil {
|
|
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
|
|
if logErrors {
|
|
retErr.Log(t.l)
|
|
} else {
|
|
return retErr
|
|
}
|
|
} else {
|
|
t.l.WithField("route", r).Info("Added route")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rm *tun) RemoveRoutes(t *wgTun, routes []Route) {
|
|
name, err := t.tunDevice.Name()
|
|
if err != nil {
|
|
t.l.WithError(err).Error("Failed to get device name for route removal")
|
|
return
|
|
}
|
|
|
|
for _, r := range routes {
|
|
if !r.Install {
|
|
continue
|
|
}
|
|
|
|
args := []string{"delete"}
|
|
|
|
if r.Cidr.Addr().Is6() {
|
|
args = append(args, "-inet6")
|
|
} else {
|
|
args = append(args, "-inet")
|
|
}
|
|
|
|
args = append(args, r.Cidr.String(), "-interface", name)
|
|
|
|
err := runCommandBSD("route", args...)
|
|
if err != nil {
|
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
|
} else {
|
|
t.l.WithField("route", r).Info("Removed route")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rm *tun) NewMultiQueueReader(t *wgTun) (io.ReadWriteCloser, error) {
|
|
// FreeBSD doesn't support multi-queue TUN devices in the same way as Linux
|
|
// Return a reader that wraps the same device
|
|
return &wgTunReader{
|
|
parent: t,
|
|
tunDevice: t.tunDevice,
|
|
offset: 0,
|
|
l: t.l,
|
|
}, nil
|
|
}
|
|
|
|
func (rm *tun) addIP(t *wgTun, name string, network netip.Prefix) error {
|
|
addr := network.Addr()
|
|
|
|
if addr.Is4() {
|
|
// For IPv4: ifconfig tun0 10.0.0.1/24
|
|
if err := runCommandBSD("ifconfig", name, network.String()); err != nil {
|
|
return fmt.Errorf("failed to add IPv4 address: %w", err)
|
|
}
|
|
} else {
|
|
// For IPv6: ifconfig tun0 inet6 add 2001:db8::1/64
|
|
if err := runCommandBSD("ifconfig", name, "inet6", "add", network.String()); err != nil {
|
|
return fmt.Errorf("failed to add IPv6 address: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runCommandBSD(name string, args ...string) error {
|
|
cmd := exec.Command(name, args...)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("%s %s failed: %w\nOutput: %s", name, strings.Join(args, " "), err, string(output))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func renameInterface(fromName, toName string) error {
|
|
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create socket: %w", err)
|
|
}
|
|
defer syscall.Close(s)
|
|
|
|
fd := uintptr(s)
|
|
|
|
var fromNameBytes [unix.IFNAMSIZ]byte
|
|
var toNameBytes [unix.IFNAMSIZ]byte
|
|
copy(fromNameBytes[:], fromName)
|
|
copy(toNameBytes[:], toName)
|
|
|
|
ifrr := ifreqRename{
|
|
Name: fromNameBytes,
|
|
Data: uintptr(unsafe.Pointer(&toNameBytes)),
|
|
}
|
|
|
|
// Set the device name using SIOCSIFNAME ioctl
|
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("SIOCSIFNAME ioctl failed: %w", errno)
|
|
}
|
|
|
|
return nil
|
|
}
|