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 }