diff --git a/Makefile b/Makefile index 7eaa07f..7d9d497 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,8 @@ ALL = $(ALL_LINUX) \ darwin-amd64 \ darwin-arm64 \ windows-amd64 \ - windows-arm64 + windows-arm64 \ + netbsd-amd64 e2e: $(TEST_ENV) go test -tags=e2e_testing -count=1 $(TEST_FLAGS) ./e2e diff --git a/examples/config.yml b/examples/config.yml index 23ff505..5c28fea 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -207,6 +207,7 @@ 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 NetBSD: 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_netbsd.go b/overlay/tun_netbsd.go new file mode 100644 index 0000000..cd17149 --- /dev/null +++ b/overlay/tun_netbsd.go @@ -0,0 +1,156 @@ +//go:build !e2e_testing +// +build !e2e_testing + +package overlay + +import ( + "fmt" + "io" + "net" + "os" + "os/exec" + "regexp" + "strconv" + "syscall" + "unsafe" + + "github.com/sirupsen/logrus" + "github.com/slackhq/nebula/cidr" + "github.com/slackhq/nebula/iputil" +) + +type ifreqDestroy struct { + Name [16]byte + pad [16]byte +} + +type tun struct { + Device string + cidr *net.IPNet + MTU int + Routes []Route + routeTree *cidr.Tree4 + l *logrus.Logger + + io.ReadWriteCloser +} + +func (t *tun) Close() error { + if t.ReadWriteCloser != nil { + 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()} + + err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq))) + + return err + } + return nil +} + +func newTunFromFd(_ *logrus.Logger, _ int, _ *net.IPNet, _ int, _ []Route, _ int, _ bool) (*tun, error) { + return nil, fmt.Errorf("newTunFromFd not supported in NetBSD") +} + +var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`) + +func newTun(l *logrus.Logger, deviceName string, cidr *net.IPNet, defaultMTU int, routes []Route, _ int, _ bool, _ bool) (*tun, error) { + // Try to open tun device + var file *os.File + var err error + if deviceName == "" { + return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified") + } + if !deviceNameRE.MatchString(deviceName) { + return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified") + } + file, err = os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0) + + if err != nil { + return nil, err + } + + routeTree, err := makeRouteTree(l, routes, false) + + if err != nil { + return nil, err + } + + return &tun{ + ReadWriteCloser: file, + Device: deviceName, + cidr: cidr, + MTU: defaultMTU, + Routes: routes, + routeTree: routeTree, + l: l, + }, nil +} + +func (t *tun) Activate() error { + var err error + + // 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 { + return fmt.Errorf("failed to run 'ifconfig': %s", err) + } + t.l.Debug("command: route", "-n", "add", "-net", t.cidr.String(), t.cidr.IP.String()) + if err = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), t.cidr.IP.String()).Run(); err != nil { + return fmt.Errorf("failed to run 'route add': %s", err) + } + t.l.Debug("command: ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU)) + if err = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU)).Run(); err != nil { + return fmt.Errorf("failed to run 'ifconfig': %s", err) + } + // Unsafe path routes + for _, r := range t.Routes { + if r.Via == nil || !r.Install { + // We don't allow route MTUs so only install routes with a via + continue + } + + t.l.Debug("command: route", "-n", "add", "-net", r.Cidr.String(), t.cidr.IP.String()) + if err = exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), t.cidr.IP.String()).Run(); err != nil { + return fmt.Errorf("failed to run 'route add' for unsafe_route %s: %s", r.Cidr.String(), err) + } + } + + return nil +} + +func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp { + r := t.routeTree.MostSpecificContains(ip) + if r != nil { + return r.(iputil.VpnIp) + } + + return 0 +} + +func (t *tun) Cidr() *net.IPNet { + return t.cidr +} + +func (t *tun) Name() string { + return t.Device +} + +func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) { + return nil, fmt.Errorf("TODO: multiqueue not implemented for netbsd") +} + +func (t *tun) deviceBytes() (o [16]byte) { + for i, c := range t.Device { + o[i] = byte(c) + } + return +} diff --git a/udp/udp_netbsd.go b/udp/udp_netbsd.go new file mode 100644 index 0000000..3c14fac --- /dev/null +++ b/udp/udp_netbsd.go @@ -0,0 +1,46 @@ +//go:build !e2e_testing +// +build !e2e_testing + +package udp + +// FreeBSD support is primarily implemented in udp_generic, besides NewListenConfig + +import ( + "fmt" + "net" + "syscall" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +func NewListener(l *logrus.Logger, ip net.IP, port int, multi bool, batch int) (Conn, error) { + return NewGenericListener(l, ip, port, multi, batch) +} + +func NewListenConfig(multi bool) net.ListenConfig { + return net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) error { + if multi { + var controlErr error + err := c.Control(func(fd uintptr) { + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { + controlErr = fmt.Errorf("SO_REUSEPORT failed: %v", err) + return + } + }) + if err != nil { + return err + } + if controlErr != nil { + return controlErr + } + } + return nil + }, + } +} + +func (u *GenericConn) Rebind() error { + return nil +}