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