github.com/slackhq/nebula@v1.9.0/overlay/tun_freebsd.go (about)

     1  //go:build !e2e_testing
     2  // +build !e2e_testing
     3  
     4  package overlay
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/fs"
    12  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"strconv"
    16  	"sync/atomic"
    17  	"syscall"
    18  	"unsafe"
    19  
    20  	"github.com/sirupsen/logrus"
    21  	"github.com/slackhq/nebula/cidr"
    22  	"github.com/slackhq/nebula/config"
    23  	"github.com/slackhq/nebula/iputil"
    24  	"github.com/slackhq/nebula/util"
    25  )
    26  
    27  const (
    28  	// FIODGNAME is defined in sys/sys/filio.h on FreeBSD
    29  	// For 32-bit systems, use FIODGNAME_32 (not defined in this file: 0x80086678)
    30  	FIODGNAME = 0x80106678
    31  )
    32  
    33  type fiodgnameArg struct {
    34  	length int32
    35  	pad    [4]byte
    36  	buf    unsafe.Pointer
    37  }
    38  
    39  type ifreqRename struct {
    40  	Name [16]byte
    41  	Data uintptr
    42  }
    43  
    44  type ifreqDestroy struct {
    45  	Name [16]byte
    46  	pad  [16]byte
    47  }
    48  
    49  type tun struct {
    50  	Device    string
    51  	cidr      *net.IPNet
    52  	MTU       int
    53  	Routes    atomic.Pointer[[]Route]
    54  	routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
    55  	l         *logrus.Logger
    56  
    57  	io.ReadWriteCloser
    58  }
    59  
    60  func (t *tun) Close() error {
    61  	if t.ReadWriteCloser != nil {
    62  		if err := t.ReadWriteCloser.Close(); err != nil {
    63  			return err
    64  		}
    65  
    66  		s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
    67  		if err != nil {
    68  			return err
    69  		}
    70  		defer syscall.Close(s)
    71  
    72  		ifreq := ifreqDestroy{Name: t.deviceBytes()}
    73  
    74  		// Destroy the interface
    75  		err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
    76  		return err
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) {
    83  	return nil, fmt.Errorf("newTunFromFd not supported in FreeBSD")
    84  }
    85  
    86  func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) {
    87  	// Try to open existing tun device
    88  	var file *os.File
    89  	var err error
    90  	deviceName := c.GetString("tun.dev", "")
    91  	if deviceName != "" {
    92  		file, err = os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0)
    93  	}
    94  	if errors.Is(err, fs.ErrNotExist) || deviceName == "" {
    95  		// If the device doesn't already exist, request a new one and rename it
    96  		file, err = os.OpenFile("/dev/tun", os.O_RDWR, 0)
    97  	}
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	rawConn, err := file.SyscallConn()
   103  	if err != nil {
   104  		return nil, fmt.Errorf("SyscallConn: %v", err)
   105  	}
   106  
   107  	var name [16]byte
   108  	var ctrlErr error
   109  	rawConn.Control(func(fd uintptr) {
   110  		// Read the name of the interface
   111  		arg := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)}
   112  		ctrlErr = ioctl(fd, FIODGNAME, uintptr(unsafe.Pointer(&arg)))
   113  	})
   114  	if ctrlErr != nil {
   115  		return nil, err
   116  	}
   117  
   118  	ifName := string(bytes.TrimRight(name[:], "\x00"))
   119  	if deviceName == "" {
   120  		deviceName = ifName
   121  	}
   122  
   123  	// If the name doesn't match the desired interface name, rename it now
   124  	if ifName != deviceName {
   125  		s, err := syscall.Socket(
   126  			syscall.AF_INET,
   127  			syscall.SOCK_DGRAM,
   128  			syscall.IPPROTO_IP,
   129  		)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		defer syscall.Close(s)
   134  
   135  		fd := uintptr(s)
   136  
   137  		var fromName [16]byte
   138  		var toName [16]byte
   139  		copy(fromName[:], ifName)
   140  		copy(toName[:], deviceName)
   141  
   142  		ifrr := ifreqRename{
   143  			Name: fromName,
   144  			Data: uintptr(unsafe.Pointer(&toName)),
   145  		}
   146  
   147  		// Set the device name
   148  		ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
   149  	}
   150  
   151  	t := &tun{
   152  		ReadWriteCloser: file,
   153  		Device:          deviceName,
   154  		cidr:            cidr,
   155  		MTU:             c.GetInt("tun.mtu", DefaultMTU),
   156  		l:               l,
   157  	}
   158  
   159  	err = t.reload(c, true)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	c.RegisterReloadCallback(func(c *config.C) {
   165  		err := t.reload(c, false)
   166  		if err != nil {
   167  			util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
   168  		}
   169  	})
   170  
   171  	return t, nil
   172  }
   173  
   174  func (t *tun) Activate() error {
   175  	var err error
   176  	// TODO use syscalls instead of exec.Command
   177  	cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String())
   178  	t.l.Debug("command: ", cmd.String())
   179  	if err = cmd.Run(); err != nil {
   180  		return fmt.Errorf("failed to run 'ifconfig': %s", err)
   181  	}
   182  
   183  	cmd = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), "-interface", t.Device)
   184  	t.l.Debug("command: ", cmd.String())
   185  	if err = cmd.Run(); err != nil {
   186  		return fmt.Errorf("failed to run 'route add': %s", err)
   187  	}
   188  
   189  	cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU))
   190  	t.l.Debug("command: ", cmd.String())
   191  	if err = cmd.Run(); err != nil {
   192  		return fmt.Errorf("failed to run 'ifconfig': %s", err)
   193  	}
   194  
   195  	// Unsafe path routes
   196  	return t.addRoutes(false)
   197  }
   198  
   199  func (t *tun) reload(c *config.C, initial bool) error {
   200  	change, routes, err := getAllRoutesFromConfig(c, t.cidr, initial)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	if !initial && !change {
   206  		return nil
   207  	}
   208  
   209  	routeTree, err := makeRouteTree(t.l, routes, false)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	// Teach nebula how to handle the routes before establishing them in the system table
   215  	oldRoutes := t.Routes.Swap(&routes)
   216  	t.routeTree.Store(routeTree)
   217  
   218  	if !initial {
   219  		// Remove first, if the system removes a wanted route hopefully it will be re-added next
   220  		err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
   221  		if err != nil {
   222  			util.LogWithContextIfNeeded("Failed to remove routes", err, t.l)
   223  		}
   224  
   225  		// Ensure any routes we actually want are installed
   226  		err = t.addRoutes(true)
   227  		if err != nil {
   228  			// Catch any stray logs
   229  			util.LogWithContextIfNeeded("Failed to add routes", err, t.l)
   230  		}
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
   237  	_, r := t.routeTree.Load().MostSpecificContains(ip)
   238  	return r
   239  }
   240  
   241  func (t *tun) Cidr() *net.IPNet {
   242  	return t.cidr
   243  }
   244  
   245  func (t *tun) Name() string {
   246  	return t.Device
   247  }
   248  
   249  func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
   250  	return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd")
   251  }
   252  
   253  func (t *tun) addRoutes(logErrors bool) error {
   254  	routes := *t.Routes.Load()
   255  	for _, r := range routes {
   256  		if r.Via == nil || !r.Install {
   257  			// We don't allow route MTUs so only install routes with a via
   258  			continue
   259  		}
   260  
   261  		cmd := exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), "-interface", t.Device)
   262  		t.l.Debug("command: ", cmd.String())
   263  		if err := cmd.Run(); err != nil {
   264  			retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]interface{}{"route": r}, err)
   265  			if logErrors {
   266  				retErr.Log(t.l)
   267  			} else {
   268  				return retErr
   269  			}
   270  		}
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  func (t *tun) removeRoutes(routes []Route) error {
   277  	for _, r := range routes {
   278  		if !r.Install {
   279  			continue
   280  		}
   281  
   282  		cmd := exec.Command("/sbin/route", "-n", "delete", "-net", r.Cidr.String(), "-interface", t.Device)
   283  		t.l.Debug("command: ", cmd.String())
   284  		if err := cmd.Run(); err != nil {
   285  			t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
   286  		} else {
   287  			t.l.WithField("route", r).Info("Removed route")
   288  		}
   289  	}
   290  	return nil
   291  }
   292  
   293  func (t *tun) deviceBytes() (o [16]byte) {
   294  	for i, c := range t.Device {
   295  		o[i] = byte(c)
   296  	}
   297  	return
   298  }