diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5590d3fb..f4efa389d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ name: Create release and upload binaries jobs: build-linux: - name: Build Linux All + name: Build Linux/BSD All runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -19,7 +19,7 @@ jobs: - name: Build run: | - make BUILD_NUMBER="${GITHUB_REF#refs/tags/v}" release-linux release-freebsd + make BUILD_NUMBER="${GITHUB_REF#refs/tags/v}" release-linux release-freebsd release-openbsd release-netbsd mkdir release mv build/*.tar.gz release diff --git a/Makefile b/Makefile index 7d9d49733..a70e4ae41 100644 --- a/Makefile +++ b/Makefile @@ -49,13 +49,20 @@ ALL_LINUX = linux-amd64 \ ALL_FREEBSD = freebsd-amd64 \ freebsd-arm64 +ALL_OPENBSD = openbsd-amd64 \ + openbsd-arm64 + +ALL_NETBSD = netbsd-amd64 \ + netbsd-arm64 + ALL = $(ALL_LINUX) \ $(ALL_FREEBSD) \ + $(ALL_OPENBSD) \ + $(ALL_NETBSD) \ darwin-amd64 \ darwin-arm64 \ windows-amd64 \ - windows-arm64 \ - netbsd-amd64 + windows-arm64 e2e: $(TEST_ENV) go test -tags=e2e_testing -count=1 $(TEST_FLAGS) ./e2e @@ -83,6 +90,10 @@ release-linux: $(ALL_LINUX:%=build/nebula-%.tar.gz) release-freebsd: $(ALL_FREEBSD:%=build/nebula-%.tar.gz) +release-openbsd: $(ALL_OPENBSD:%=build/nebula-%.tar.gz) + +release-netbsd: $(ALL_NETBSD:%=build/nebula-%.tar.gz) + release-boringcrypto: build/nebula-linux-$(shell go env GOARCH)-boringcrypto.tar.gz BUILD_ARGS = -trimpath diff --git a/overlay/tun_netbsd.go b/overlay/tun_netbsd.go index cd171496e..4d7f89751 100644 --- a/overlay/tun_netbsd.go +++ b/overlay/tun_netbsd.go @@ -99,16 +99,21 @@ 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 { + cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String()) + t.l.Debug("command: ", cmd.String()) + if err = cmd.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 { + + cmd = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), t.cidr.IP.String()) + t.l.Debug("command: ", cmd.String()) + if err = cmd.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 { + + cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU)) + t.l.Debug("command: ", cmd.String()) + if err = cmd.Run(); err != nil { return fmt.Errorf("failed to run 'ifconfig': %s", err) } // Unsafe path routes @@ -118,8 +123,9 @@ func (t *tun) Activate() error { 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 { + cmd = exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), t.cidr.IP.String()) + t.l.Debug("command: ", cmd.String()) + if err = cmd.Run(); err != nil { return fmt.Errorf("failed to run 'route add' for unsafe_route %s: %s", r.Cidr.String(), err) } } diff --git a/overlay/tun_openbsd.go b/overlay/tun_openbsd.go new file mode 100644 index 000000000..709fb427f --- /dev/null +++ b/overlay/tun_openbsd.go @@ -0,0 +1,174 @@ +//go:build !e2e_testing +// +build !e2e_testing + +package overlay + +import ( + "fmt" + "io" + "net" + "os" + "os/exec" + "regexp" + "strconv" + "syscall" + + "github.com/sirupsen/logrus" + "github.com/slackhq/nebula/cidr" + "github.com/slackhq/nebula/iputil" +) + +type tun struct { + Device string + cidr *net.IPNet + MTU int + Routes []Route + routeTree *cidr.Tree4 + l *logrus.Logger + + io.ReadWriteCloser + + // cache out buffer since we need to prepend 4 bytes for tun metadata + out []byte +} + +func (t *tun) Close() error { + if t.ReadWriteCloser != nil { + return t.ReadWriteCloser.Close() + } + + return nil +} + +func newTunFromFd(_ *logrus.Logger, _ int, _ *net.IPNet, _ int, _ []Route, _ int, _ bool) (*tun, error) { + return nil, fmt.Errorf("newTunFromFd not supported in OpenBSD") +} + +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) { + if deviceName == "" { + return nil, fmt.Errorf("a device name in the format of tunN must be specified") + } + + if !deviceNameRE.MatchString(deviceName) { + return nil, fmt.Errorf("a device name in the format of 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 + cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String()) + t.l.Debug("command: ", cmd.String()) + if err = cmd.Run(); err != nil { + return fmt.Errorf("failed to run 'ifconfig': %s", err) + } + + cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU)) + t.l.Debug("command: ", cmd.String()) + if err = cmd.Run(); err != nil { + return fmt.Errorf("failed to run 'ifconfig': %s", err) + } + + cmd = exec.Command("/sbin/route", "-n", "add", "-inet", t.cidr.String(), t.cidr.IP.String()) + t.l.Debug("command: ", cmd.String()) + if err = cmd.Run(); err != nil { + return fmt.Errorf("failed to run 'route add': %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 + } + + cmd = exec.Command("/sbin/route", "-n", "add", "-inet", r.Cidr.String(), t.cidr.IP.String()) + t.l.Debug("command: ", cmd.String()) + if err = cmd.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 freebsd") +} + +func (t *tun) Read(to []byte) (int, error) { + buf := make([]byte, len(to)+4) + + n, err := t.ReadWriteCloser.Read(buf) + + copy(to, buf[4:]) + return n - 4, err +} + +// Write is only valid for single threaded use +func (t *tun) Write(from []byte) (int, error) { + buf := t.out + if cap(buf) < len(from)+4 { + buf = make([]byte, len(from)+4) + t.out = buf + } + buf = buf[:len(from)+4] + + if len(from) == 0 { + return 0, syscall.EIO + } + + // Determine the IP Family for the NULL L2 Header + ipVer := from[0] >> 4 + if ipVer == 4 { + buf[3] = syscall.AF_INET + } else if ipVer == 6 { + buf[3] = syscall.AF_INET6 + } else { + return 0, fmt.Errorf("unable to determine IP version from packet") + } + + copy(buf[4:], from) + + n, err := t.ReadWriteCloser.Write(buf) + return n - 4, err +} diff --git a/udp/udp_freebsd.go b/udp/udp_bsd.go similarity index 92% rename from udp/udp_freebsd.go rename to udp/udp_bsd.go index 3c14face3..785aa6a74 100644 --- a/udp/udp_freebsd.go +++ b/udp/udp_bsd.go @@ -1,4 +1,5 @@ -//go:build !e2e_testing +//go:build (openbsd || freebsd) && !e2e_testing +// +build openbsd freebsd // +build !e2e_testing package udp