From 36daea95511637dbf1b907927ba131ee8c8cc3ec Mon Sep 17 00:00:00 2001 From: JackDoan Date: Thu, 2 Oct 2025 16:18:34 -0500 Subject: [PATCH] linux: opt out of naming your tun device yourself --- overlay/tun_linux.go | 45 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/overlay/tun_linux.go b/overlay/tun_linux.go index 44d8746..8bd08cb 100644 --- a/overlay/tun_linux.go +++ b/overlay/tun_linux.go @@ -4,6 +4,7 @@ package overlay import ( + "errors" "fmt" "io" "net" @@ -101,12 +102,18 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu } } + tunNameTemplate := c.GetString("tun.dev", "nebula%d") + tunName, err := findNextTunName(tunNameTemplate) + if err != nil { + return nil, err + } + var req ifReq req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI) if multiqueue { req.Flags |= unix.IFF_MULTI_QUEUE } - copy(req.Name[:], c.GetString("tun.dev", "")) + copy(req.Name[:], tunName) if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil { return nil, err } @@ -123,6 +130,38 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu return t, nil } +func findNextTunName(tunName string) (string, error) { + if !strings.HasSuffix(tunName, "%d") { + return tunName, nil + } + if len(tunName) == 2 { + return "", errors.New("please don't name your tun device '%d'") + } + tunNameTemplate := tunName[:len(tunName)-2] + links, err := netlink.LinkList() + if err != nil { + return "", err + } + var candidateName string + for i := 0; i < 100000; i++ { + candidateName = fmt.Sprintf("%s%d", tunNameTemplate, i) + good := true + for _, link := range links { + if candidateName == link.Attrs().Name { + good = false + break + } + } + if good { + if len(candidateName) > 16 { + return "", errors.New("you have too many nebula networks") + } + return candidateName, nil + } + } + return "", errors.New("failed to find a tun device name") +} + func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []netip.Prefix) (*tun, error) { t := &tun{ ReadWriteCloser: file, @@ -582,9 +621,7 @@ func (t *tun) isGatewayInVpnNetworks(gwAddr netip.Addr) bool { } func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways { - var gateways routing.Gateways - link, err := netlink.LinkByName(t.Device) if err != nil { t.l.WithField("Devicename", t.Device).Error("Ignoring route update: failed to get link by name") @@ -633,9 +670,7 @@ func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways { } func (t *tun) updateRoutes(r netlink.RouteUpdate) { - gateways := t.getGatewaysFromRoute(&r.Route) - if len(gateways) == 0 { // No gateways relevant to our network, no routing changes required. t.l.WithField("route", r).Debug("Ignoring route update, no gateways")