| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- //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
- }
|