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

     1  //go:build !e2e_testing
     2  // +build !e2e_testing
     3  
     4  package overlay
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"os"
    11  	"os/exec"
    12  	"regexp"
    13  	"strconv"
    14  	"sync/atomic"
    15  	"syscall"
    16  	"unsafe"
    17  
    18  	"github.com/sirupsen/logrus"
    19  	"github.com/slackhq/nebula/cidr"
    20  	"github.com/slackhq/nebula/config"
    21  	"github.com/slackhq/nebula/iputil"
    22  	"github.com/slackhq/nebula/util"
    23  )
    24  
    25  type ifreqDestroy struct {
    26  	Name [16]byte
    27  	pad  [16]byte
    28  }
    29  
    30  type tun struct {
    31  	Device    string
    32  	cidr      *net.IPNet
    33  	MTU       int
    34  	Routes    atomic.Pointer[[]Route]
    35  	routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
    36  	l         *logrus.Logger
    37  
    38  	io.ReadWriteCloser
    39  }
    40  
    41  func (t *tun) Close() error {
    42  	if t.ReadWriteCloser != nil {
    43  		if err := t.ReadWriteCloser.Close(); err != nil {
    44  			return err
    45  		}
    46  
    47  		s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
    48  		if err != nil {
    49  			return err
    50  		}
    51  		defer syscall.Close(s)
    52  
    53  		ifreq := ifreqDestroy{Name: t.deviceBytes()}
    54  
    55  		err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
    56  
    57  		return err
    58  	}
    59  	return nil
    60  }
    61  
    62  func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) {
    63  	return nil, fmt.Errorf("newTunFromFd not supported in NetBSD")
    64  }
    65  
    66  var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
    67  
    68  func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) {
    69  	// Try to open tun device
    70  	var file *os.File
    71  	var err error
    72  	deviceName := c.GetString("tun.dev", "")
    73  	if deviceName == "" {
    74  		return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
    75  	}
    76  	if !deviceNameRE.MatchString(deviceName) {
    77  		return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
    78  	}
    79  
    80  	file, err = os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	t := &tun{
    86  		ReadWriteCloser: file,
    87  		Device:          deviceName,
    88  		cidr:            cidr,
    89  		MTU:             c.GetInt("tun.mtu", DefaultMTU),
    90  		l:               l,
    91  	}
    92  
    93  	err = t.reload(c, true)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	c.RegisterReloadCallback(func(c *config.C) {
    99  		err := t.reload(c, false)
   100  		if err != nil {
   101  			util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
   102  		}
   103  	})
   104  
   105  	return t, nil
   106  }
   107  
   108  func (t *tun) Activate() error {
   109  	var err error
   110  
   111  	// TODO use syscalls instead of exec.Command
   112  	cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String())
   113  	t.l.Debug("command: ", cmd.String())
   114  	if err = cmd.Run(); err != nil {
   115  		return fmt.Errorf("failed to run 'ifconfig': %s", err)
   116  	}
   117  
   118  	cmd = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), t.cidr.IP.String())
   119  	t.l.Debug("command: ", cmd.String())
   120  	if err = cmd.Run(); err != nil {
   121  		return fmt.Errorf("failed to run 'route add': %s", err)
   122  	}
   123  
   124  	cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU))
   125  	t.l.Debug("command: ", cmd.String())
   126  	if err = cmd.Run(); err != nil {
   127  		return fmt.Errorf("failed to run 'ifconfig': %s", err)
   128  	}
   129  
   130  	// Unsafe path routes
   131  	return t.addRoutes(false)
   132  }
   133  
   134  func (t *tun) reload(c *config.C, initial bool) error {
   135  	change, routes, err := getAllRoutesFromConfig(c, t.cidr, initial)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	if !initial && !change {
   141  		return nil
   142  	}
   143  
   144  	routeTree, err := makeRouteTree(t.l, routes, false)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	// Teach nebula how to handle the routes before establishing them in the system table
   150  	oldRoutes := t.Routes.Swap(&routes)
   151  	t.routeTree.Store(routeTree)
   152  
   153  	if !initial {
   154  		// Remove first, if the system removes a wanted route hopefully it will be re-added next
   155  		err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
   156  		if err != nil {
   157  			util.LogWithContextIfNeeded("Failed to remove routes", err, t.l)
   158  		}
   159  
   160  		// Ensure any routes we actually want are installed
   161  		err = t.addRoutes(true)
   162  		if err != nil {
   163  			// Catch any stray logs
   164  			util.LogWithContextIfNeeded("Failed to add routes", err, t.l)
   165  		}
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
   172  	_, r := t.routeTree.Load().MostSpecificContains(ip)
   173  	return r
   174  }
   175  
   176  func (t *tun) Cidr() *net.IPNet {
   177  	return t.cidr
   178  }
   179  
   180  func (t *tun) Name() string {
   181  	return t.Device
   182  }
   183  
   184  func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
   185  	return nil, fmt.Errorf("TODO: multiqueue not implemented for netbsd")
   186  }
   187  
   188  func (t *tun) addRoutes(logErrors bool) error {
   189  	routes := *t.Routes.Load()
   190  	for _, r := range routes {
   191  		if r.Via == nil || !r.Install {
   192  			// We don't allow route MTUs so only install routes with a via
   193  			continue
   194  		}
   195  
   196  		cmd := exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), t.cidr.IP.String())
   197  		t.l.Debug("command: ", cmd.String())
   198  		if err := cmd.Run(); err != nil {
   199  			retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]interface{}{"route": r}, err)
   200  			if logErrors {
   201  				retErr.Log(t.l)
   202  			} else {
   203  				return retErr
   204  			}
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func (t *tun) removeRoutes(routes []Route) error {
   212  	for _, r := range routes {
   213  		if !r.Install {
   214  			continue
   215  		}
   216  
   217  		cmd := exec.Command("/sbin/route", "-n", "delete", "-net", r.Cidr.String(), t.cidr.IP.String())
   218  		t.l.Debug("command: ", cmd.String())
   219  		if err := cmd.Run(); err != nil {
   220  			t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
   221  		} else {
   222  			t.l.WithField("route", r).Info("Removed route")
   223  		}
   224  	}
   225  	return nil
   226  }
   227  
   228  func (t *tun) deviceBytes() (o [16]byte) {
   229  	for i, c := range t.Device {
   230  		o[i] = byte(c)
   231  	}
   232  	return
   233  }