diff --git a/Makefile b/Makefile index 68f5ca7..fecd889 100644 --- a/Makefile +++ b/Makefile @@ -44,10 +44,13 @@ ALL_LINUX = linux-amd64 \ linux-mips-softfloat \ linux-riscv64 +ALL_FREEBSD = freebsd-amd64 \ + freebsd-arm64 + ALL = $(ALL_LINUX) \ + $(ALL_FREEBSD) \ darwin-amd64 \ darwin-arm64 \ - freebsd-amd64 \ windows-amd64 \ windows-arm64 @@ -75,7 +78,7 @@ release: $(ALL:%=build/nebula-%.tar.gz) release-linux: $(ALL_LINUX:%=build/nebula-%.tar.gz) -release-freebsd: build/nebula-freebsd-amd64.tar.gz +release-freebsd: $(ALL_FREEBSD:%=build/nebula-%.tar.gz) release-boringcrypto: build/nebula-linux-$(shell go env GOARCH)-boringcrypto.tar.gz @@ -93,6 +96,9 @@ bin-darwin: build/darwin-amd64/nebula build/darwin-amd64/nebula-cert bin-freebsd: build/freebsd-amd64/nebula build/freebsd-amd64/nebula-cert mv $? . +bin-freebsd-arm64: build/freebsd-arm64/nebula build/freebsd-arm64/nebula-cert + mv $? . + bin-boringcrypto: build/linux-$(shell go env GOARCH)-boringcrypto/nebula build/linux-$(shell go env GOARCH)-boringcrypto/nebula-cert mv $? . diff --git a/examples/config.yml b/examples/config.yml index ad49a3c..a7acb73 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -194,7 +194,6 @@ tun: disabled: false # Name of the device. If not set, a default will be chosen by the OS. # For macOS: if set, must be in the form `utun[0-9]+`. - # For FreeBSD: Required to be set, must be in the form `tun[0-9]+`. dev: nebula1 # Toggles forwarding of local broadcast packets, the address of which depends on the ip/mask encoded in pki.cert drop_local_broadcast: false diff --git a/overlay/tun_darwin.go b/overlay/tun_darwin.go index fd3429d..428e38f 100644 --- a/overlay/tun_darwin.go +++ b/overlay/tun_darwin.go @@ -47,14 +47,6 @@ type ifReq struct { pad [8]byte } -func ioctl(a1, a2, a3 uintptr) error { - _, _, errno := unix.Syscall(unix.SYS_IOCTL, a1, a2, a3) - if errno != 0 { - return errno - } - return nil -} - var sockaddrCtlSize uintptr = 32 const ( @@ -194,10 +186,10 @@ func (t *tun) Activate() error { unix.SOCK_DGRAM, unix.IPPROTO_IP, ) - if err != nil { return err } + defer unix.Close(s) fd := uintptr(s) diff --git a/overlay/tun_freebsd.go b/overlay/tun_freebsd.go index 99cbdb0..8a52954 100644 --- a/overlay/tun_freebsd.go +++ b/overlay/tun_freebsd.go @@ -4,21 +4,44 @@ package overlay import ( + "bytes" + "errors" "fmt" "io" + "io/fs" "net" "os" "os/exec" - "regexp" "strconv" - "strings" + "syscall" + "unsafe" "github.com/sirupsen/logrus" "github.com/slackhq/nebula/cidr" "github.com/slackhq/nebula/iputil" ) -var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`) +const ( + // FIODGNAME is defined in sys/sys/filio.h on FreeBSD + // For 32-bit systems, use FIODGNAME_32 (not defined in this file: 0x80086678) + FIODGNAME = 0x80106678 +) + +type fiodgnameArg struct { + length int32 + pad [4]byte + buf unsafe.Pointer +} + +type ifreqRename struct { + Name [16]byte + Data uintptr +} + +type ifreqDestroy struct { + Name [16]byte + pad [16]byte +} type tun struct { Device string @@ -33,8 +56,23 @@ type tun struct { func (t *tun) Close() error { if t.ReadWriteCloser != nil { - return t.ReadWriteCloser.Close() + if err := t.ReadWriteCloser.Close(); err != nil { + return err + } + + s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP) + if err != nil { + return err + } + defer syscall.Close(s) + + ifreq := ifreqDestroy{Name: t.deviceBytes()} + + // Destroy the interface + err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq))) + return err } + return nil } @@ -43,34 +81,87 @@ func newTunFromFd(_ *logrus.Logger, _ int, _ *net.IPNet, _ int, _ []Route, _ int } func newTun(l *logrus.Logger, deviceName string, cidr *net.IPNet, defaultMTU int, routes []Route, _ int, _ bool, _ bool) (*tun, error) { + // Try to open existing tun device + var file *os.File + var err error + if deviceName != "" { + file, err = os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0) + } + if errors.Is(err, fs.ErrNotExist) || deviceName == "" { + // If the device doesn't already exist, request a new one and rename it + file, err = os.OpenFile("/dev/tun", os.O_RDWR, 0) + } + if err != nil { + return nil, err + } + + rawConn, err := file.SyscallConn() + if err != nil { + return nil, fmt.Errorf("SyscallConn: %v", err) + } + + var name [16]byte + var ctrlErr error + rawConn.Control(func(fd uintptr) { + // Read the name of the interface + arg := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)} + ctrlErr = ioctl(fd, FIODGNAME, uintptr(unsafe.Pointer(&arg))) + }) + if ctrlErr != nil { + return nil, err + } + + ifName := string(bytes.TrimRight(name[:], "\x00")) + if deviceName == "" { + deviceName = ifName + } + + // If the name doesn't match the desired interface name, rename it now + if ifName != deviceName { + s, err := syscall.Socket( + syscall.AF_INET, + syscall.SOCK_DGRAM, + syscall.IPPROTO_IP, + ) + if err != nil { + return nil, err + } + defer syscall.Close(s) + + fd := uintptr(s) + + var fromName [16]byte + var toName [16]byte + copy(fromName[:], ifName) + copy(toName[:], deviceName) + + ifrr := ifreqRename{ + Name: fromName, + Data: uintptr(unsafe.Pointer(&toName)), + } + + // Set the device name + ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr))) + } + routeTree, err := makeRouteTree(l, routes, false) if err != nil { return nil, err } - if strings.HasPrefix(deviceName, "/dev/") { - deviceName = strings.TrimPrefix(deviceName, "/dev/") - } - if !deviceNameRE.MatchString(deviceName) { - return nil, fmt.Errorf("tun.dev must match `tun[0-9]+`") - } return &tun{ - Device: deviceName, - cidr: cidr, - MTU: defaultMTU, - Routes: routes, - routeTree: routeTree, - l: l, + ReadWriteCloser: file, + Device: deviceName, + cidr: cidr, + MTU: defaultMTU, + Routes: routes, + routeTree: routeTree, + l: l, }, nil } func (t *tun) Activate() error { var err error - t.ReadWriteCloser, err = os.OpenFile("/dev/"+t.Device, os.O_RDWR, 0) - if err != nil { - return fmt.Errorf("activate failed: %v", err) - } - // TODO use syscalls instead of exec.Command t.l.Debug("command: ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String()) if err = exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String()).Run(); err != nil { @@ -120,3 +211,10 @@ func (t *tun) Name() string { func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) { return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd") } + +func (t *tun) deviceBytes() (o [16]byte) { + for i, c := range t.Device { + o[i] = byte(c) + } + return +} diff --git a/overlay/tun_linux.go b/overlay/tun_linux.go index 7833186..8751a3f 100644 --- a/overlay/tun_linux.go +++ b/overlay/tun_linux.go @@ -43,14 +43,6 @@ type ifReq struct { pad [8]byte } -func ioctl(a1, a2, a3 uintptr) error { - _, _, errno := unix.Syscall(unix.SYS_IOCTL, a1, a2, a3) - if errno != 0 { - return errno - } - return nil -} - type ifreqAddr struct { Name [16]byte Addr unix.RawSockaddrInet4 diff --git a/overlay/tun_notwin.go b/overlay/tun_notwin.go new file mode 100644 index 0000000..2fab927 --- /dev/null +++ b/overlay/tun_notwin.go @@ -0,0 +1,14 @@ +//go:build !windows +// +build !windows + +package overlay + +import "syscall" + +func ioctl(a1, a2, a3 uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, a1, a2, a3) + if errno != 0 { + return errno + } + return nil +}