github.com/slackhq/nebula@v1.9.0/overlay/tun_freebsd.go (about) 1 //go:build !e2e_testing 2 // +build !e2e_testing 3 4 package overlay 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "io" 11 "io/fs" 12 "net" 13 "os" 14 "os/exec" 15 "strconv" 16 "sync/atomic" 17 "syscall" 18 "unsafe" 19 20 "github.com/sirupsen/logrus" 21 "github.com/slackhq/nebula/cidr" 22 "github.com/slackhq/nebula/config" 23 "github.com/slackhq/nebula/iputil" 24 "github.com/slackhq/nebula/util" 25 ) 26 27 const ( 28 // FIODGNAME is defined in sys/sys/filio.h on FreeBSD 29 // For 32-bit systems, use FIODGNAME_32 (not defined in this file: 0x80086678) 30 FIODGNAME = 0x80106678 31 ) 32 33 type fiodgnameArg struct { 34 length int32 35 pad [4]byte 36 buf unsafe.Pointer 37 } 38 39 type ifreqRename struct { 40 Name [16]byte 41 Data uintptr 42 } 43 44 type ifreqDestroy struct { 45 Name [16]byte 46 pad [16]byte 47 } 48 49 type tun struct { 50 Device string 51 cidr *net.IPNet 52 MTU int 53 Routes atomic.Pointer[[]Route] 54 routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]] 55 l *logrus.Logger 56 57 io.ReadWriteCloser 58 } 59 60 func (t *tun) Close() error { 61 if t.ReadWriteCloser != nil { 62 if err := t.ReadWriteCloser.Close(); err != nil { 63 return err 64 } 65 66 s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP) 67 if err != nil { 68 return err 69 } 70 defer syscall.Close(s) 71 72 ifreq := ifreqDestroy{Name: t.deviceBytes()} 73 74 // Destroy the interface 75 err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq))) 76 return err 77 } 78 79 return nil 80 } 81 82 func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) { 83 return nil, fmt.Errorf("newTunFromFd not supported in FreeBSD") 84 } 85 86 func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) { 87 // Try to open existing tun device 88 var file *os.File 89 var err error 90 deviceName := c.GetString("tun.dev", "") 91 if deviceName != "" { 92 file, err = os.OpenFile("/dev/"+deviceName, os.O_RDWR, 0) 93 } 94 if errors.Is(err, fs.ErrNotExist) || deviceName == "" { 95 // If the device doesn't already exist, request a new one and rename it 96 file, err = os.OpenFile("/dev/tun", os.O_RDWR, 0) 97 } 98 if err != nil { 99 return nil, err 100 } 101 102 rawConn, err := file.SyscallConn() 103 if err != nil { 104 return nil, fmt.Errorf("SyscallConn: %v", err) 105 } 106 107 var name [16]byte 108 var ctrlErr error 109 rawConn.Control(func(fd uintptr) { 110 // Read the name of the interface 111 arg := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)} 112 ctrlErr = ioctl(fd, FIODGNAME, uintptr(unsafe.Pointer(&arg))) 113 }) 114 if ctrlErr != nil { 115 return nil, err 116 } 117 118 ifName := string(bytes.TrimRight(name[:], "\x00")) 119 if deviceName == "" { 120 deviceName = ifName 121 } 122 123 // If the name doesn't match the desired interface name, rename it now 124 if ifName != deviceName { 125 s, err := syscall.Socket( 126 syscall.AF_INET, 127 syscall.SOCK_DGRAM, 128 syscall.IPPROTO_IP, 129 ) 130 if err != nil { 131 return nil, err 132 } 133 defer syscall.Close(s) 134 135 fd := uintptr(s) 136 137 var fromName [16]byte 138 var toName [16]byte 139 copy(fromName[:], ifName) 140 copy(toName[:], deviceName) 141 142 ifrr := ifreqRename{ 143 Name: fromName, 144 Data: uintptr(unsafe.Pointer(&toName)), 145 } 146 147 // Set the device name 148 ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr))) 149 } 150 151 t := &tun{ 152 ReadWriteCloser: file, 153 Device: deviceName, 154 cidr: cidr, 155 MTU: c.GetInt("tun.mtu", DefaultMTU), 156 l: l, 157 } 158 159 err = t.reload(c, true) 160 if err != nil { 161 return nil, err 162 } 163 164 c.RegisterReloadCallback(func(c *config.C) { 165 err := t.reload(c, false) 166 if err != nil { 167 util.LogWithContextIfNeeded("failed to reload tun device", err, t.l) 168 } 169 }) 170 171 return t, nil 172 } 173 174 func (t *tun) Activate() error { 175 var err error 176 // TODO use syscalls instead of exec.Command 177 cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String()) 178 t.l.Debug("command: ", cmd.String()) 179 if err = cmd.Run(); err != nil { 180 return fmt.Errorf("failed to run 'ifconfig': %s", err) 181 } 182 183 cmd = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), "-interface", t.Device) 184 t.l.Debug("command: ", cmd.String()) 185 if err = cmd.Run(); err != nil { 186 return fmt.Errorf("failed to run 'route add': %s", err) 187 } 188 189 cmd = exec.Command("/sbin/ifconfig", t.Device, "mtu", strconv.Itoa(t.MTU)) 190 t.l.Debug("command: ", cmd.String()) 191 if err = cmd.Run(); err != nil { 192 return fmt.Errorf("failed to run 'ifconfig': %s", err) 193 } 194 195 // Unsafe path routes 196 return t.addRoutes(false) 197 } 198 199 func (t *tun) reload(c *config.C, initial bool) error { 200 change, routes, err := getAllRoutesFromConfig(c, t.cidr, initial) 201 if err != nil { 202 return err 203 } 204 205 if !initial && !change { 206 return nil 207 } 208 209 routeTree, err := makeRouteTree(t.l, routes, false) 210 if err != nil { 211 return err 212 } 213 214 // Teach nebula how to handle the routes before establishing them in the system table 215 oldRoutes := t.Routes.Swap(&routes) 216 t.routeTree.Store(routeTree) 217 218 if !initial { 219 // Remove first, if the system removes a wanted route hopefully it will be re-added next 220 err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes)) 221 if err != nil { 222 util.LogWithContextIfNeeded("Failed to remove routes", err, t.l) 223 } 224 225 // Ensure any routes we actually want are installed 226 err = t.addRoutes(true) 227 if err != nil { 228 // Catch any stray logs 229 util.LogWithContextIfNeeded("Failed to add routes", err, t.l) 230 } 231 } 232 233 return nil 234 } 235 236 func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp { 237 _, r := t.routeTree.Load().MostSpecificContains(ip) 238 return r 239 } 240 241 func (t *tun) Cidr() *net.IPNet { 242 return t.cidr 243 } 244 245 func (t *tun) Name() string { 246 return t.Device 247 } 248 249 func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) { 250 return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd") 251 } 252 253 func (t *tun) addRoutes(logErrors bool) error { 254 routes := *t.Routes.Load() 255 for _, r := range routes { 256 if r.Via == nil || !r.Install { 257 // We don't allow route MTUs so only install routes with a via 258 continue 259 } 260 261 cmd := exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), "-interface", t.Device) 262 t.l.Debug("command: ", cmd.String()) 263 if err := cmd.Run(); err != nil { 264 retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]interface{}{"route": r}, err) 265 if logErrors { 266 retErr.Log(t.l) 267 } else { 268 return retErr 269 } 270 } 271 } 272 273 return nil 274 } 275 276 func (t *tun) removeRoutes(routes []Route) error { 277 for _, r := range routes { 278 if !r.Install { 279 continue 280 } 281 282 cmd := exec.Command("/sbin/route", "-n", "delete", "-net", r.Cidr.String(), "-interface", t.Device) 283 t.l.Debug("command: ", cmd.String()) 284 if err := cmd.Run(); err != nil { 285 t.l.WithError(err).WithField("route", r).Error("Failed to remove route") 286 } else { 287 t.l.WithField("route", r).Info("Removed route") 288 } 289 } 290 return nil 291 } 292 293 func (t *tun) deviceBytes() (o [16]byte) { 294 for i, c := range t.Device { 295 o[i] = byte(c) 296 } 297 return 298 }