tun_freebsd.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. //go:build freebsd && !e2e_testing
  2. // +build freebsd,!e2e_testing
  3. package overlay
  4. import (
  5. "fmt"
  6. "io"
  7. "net/netip"
  8. "os/exec"
  9. "strconv"
  10. "strings"
  11. "syscall"
  12. "unsafe"
  13. "github.com/sirupsen/logrus"
  14. "github.com/slackhq/nebula/config"
  15. "github.com/slackhq/nebula/util"
  16. "golang.org/x/sys/unix"
  17. wgtun "golang.zx2c4.com/wireguard/tun"
  18. )
  19. type tun struct{}
  20. // ifreqRename is used for renaming network interfaces on FreeBSD
  21. type ifreqRename struct {
  22. Name [unix.IFNAMSIZ]byte
  23. Data uintptr
  24. }
  25. func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) {
  26. return nil, fmt.Errorf("newTunFromFd not supported on FreeBSD")
  27. }
  28. func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) {
  29. deviceName := c.GetString("tun.dev", "tun")
  30. mtu := c.GetInt("tun.mtu", DefaultMTU)
  31. // Create WireGuard TUN device
  32. tunDevice, err := wgtun.CreateTUN(deviceName, mtu)
  33. if err != nil {
  34. return nil, fmt.Errorf("failed to create TUN device: %w", err)
  35. }
  36. // Get the actual device name
  37. actualName, err := tunDevice.Name()
  38. if err != nil {
  39. tunDevice.Close()
  40. return nil, fmt.Errorf("failed to get TUN device name: %w", err)
  41. }
  42. // If the name doesn't match the desired interface name, rename it now
  43. if actualName != deviceName && deviceName != "" && deviceName != "tun" {
  44. if err := renameInterface(actualName, deviceName); err != nil {
  45. tunDevice.Close()
  46. return nil, fmt.Errorf("failed to rename interface from %s to %s: %w", actualName, deviceName, err)
  47. }
  48. actualName = deviceName
  49. }
  50. t := &wgTun{
  51. tunDevice: tunDevice,
  52. vpnNetworks: vpnNetworks,
  53. MaxMTU: mtu,
  54. DefaultMTU: mtu,
  55. l: l,
  56. }
  57. // Create FreeBSD-specific route manager
  58. t.routeManager = &tun{}
  59. err = t.reload(c, true)
  60. if err != nil {
  61. tunDevice.Close()
  62. return nil, err
  63. }
  64. c.RegisterReloadCallback(func(c *config.C) {
  65. err := t.reload(c, false)
  66. if err != nil {
  67. util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
  68. }
  69. })
  70. l.WithField("name", actualName).Info("Created WireGuard TUN device")
  71. return t, nil
  72. }
  73. func (rm *tun) Activate(t *wgTun) error {
  74. name, err := t.tunDevice.Name()
  75. if err != nil {
  76. return fmt.Errorf("failed to get device name: %w", err)
  77. }
  78. // Set the MTU
  79. rm.SetMTU(t, t.MaxMTU)
  80. // Add IP addresses
  81. for _, network := range t.vpnNetworks {
  82. if err := rm.addIP(t, name, network); err != nil {
  83. return err
  84. }
  85. }
  86. // Bring up the interface
  87. if err := runCommandBSD("ifconfig", name, "up"); err != nil {
  88. return fmt.Errorf("failed to bring up interface: %w", err)
  89. }
  90. // Set the routes
  91. if err := rm.AddRoutes(t, false); err != nil {
  92. return err
  93. }
  94. return nil
  95. }
  96. func (rm *tun) SetMTU(t *wgTun, mtu int) {
  97. name, err := t.tunDevice.Name()
  98. if err != nil {
  99. t.l.WithError(err).Error("Failed to get device name for MTU set")
  100. return
  101. }
  102. if err := runCommandBSD("ifconfig", name, "mtu", strconv.Itoa(mtu)); err != nil {
  103. t.l.WithError(err).Error("Failed to set tun mtu")
  104. }
  105. }
  106. func (rm *tun) SetDefaultRoute(t *wgTun, cidr netip.Prefix) error {
  107. // On FreeBSD, routes are set via ifconfig and route commands
  108. return nil
  109. }
  110. func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error {
  111. name, err := t.tunDevice.Name()
  112. if err != nil {
  113. return fmt.Errorf("failed to get device name: %w", err)
  114. }
  115. routes := *t.Routes.Load()
  116. for _, r := range routes {
  117. if !r.Install {
  118. continue
  119. }
  120. // Add route using route command
  121. args := []string{"add"}
  122. if r.Cidr.Addr().Is6() {
  123. args = append(args, "-inet6")
  124. } else {
  125. args = append(args, "-inet")
  126. }
  127. args = append(args, r.Cidr.String(), "-interface", name)
  128. if r.Metric > 0 {
  129. // FreeBSD doesn't support route metrics directly like Linux
  130. t.l.WithField("route", r).Warn("Route metrics are not fully supported on FreeBSD")
  131. }
  132. err := runCommandBSD("route", args...)
  133. if err != nil {
  134. retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
  135. if logErrors {
  136. retErr.Log(t.l)
  137. } else {
  138. return retErr
  139. }
  140. } else {
  141. t.l.WithField("route", r).Info("Added route")
  142. }
  143. }
  144. return nil
  145. }
  146. func (rm *tun) RemoveRoutes(t *wgTun, routes []Route) {
  147. name, err := t.tunDevice.Name()
  148. if err != nil {
  149. t.l.WithError(err).Error("Failed to get device name for route removal")
  150. return
  151. }
  152. for _, r := range routes {
  153. if !r.Install {
  154. continue
  155. }
  156. args := []string{"delete"}
  157. if r.Cidr.Addr().Is6() {
  158. args = append(args, "-inet6")
  159. } else {
  160. args = append(args, "-inet")
  161. }
  162. args = append(args, r.Cidr.String(), "-interface", name)
  163. err := runCommandBSD("route", args...)
  164. if err != nil {
  165. t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
  166. } else {
  167. t.l.WithField("route", r).Info("Removed route")
  168. }
  169. }
  170. }
  171. func (rm *tun) NewMultiQueueReader(t *wgTun) (io.ReadWriteCloser, error) {
  172. // FreeBSD doesn't support multi-queue TUN devices in the same way as Linux
  173. // Return a reader that wraps the same device
  174. return &wgTunReader{
  175. parent: t,
  176. tunDevice: t.tunDevice,
  177. offset: 0,
  178. l: t.l,
  179. }, nil
  180. }
  181. func (rm *tun) addIP(t *wgTun, name string, network netip.Prefix) error {
  182. addr := network.Addr()
  183. if addr.Is4() {
  184. // For IPv4: ifconfig tun0 10.0.0.1/24
  185. if err := runCommandBSD("ifconfig", name, network.String()); err != nil {
  186. return fmt.Errorf("failed to add IPv4 address: %w", err)
  187. }
  188. } else {
  189. // For IPv6: ifconfig tun0 inet6 add 2001:db8::1/64
  190. if err := runCommandBSD("ifconfig", name, "inet6", "add", network.String()); err != nil {
  191. return fmt.Errorf("failed to add IPv6 address: %w", err)
  192. }
  193. }
  194. return nil
  195. }
  196. func runCommandBSD(name string, args ...string) error {
  197. cmd := exec.Command(name, args...)
  198. output, err := cmd.CombinedOutput()
  199. if err != nil {
  200. return fmt.Errorf("%s %s failed: %w\nOutput: %s", name, strings.Join(args, " "), err, string(output))
  201. }
  202. return nil
  203. }
  204. func renameInterface(fromName, toName string) error {
  205. s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
  206. if err != nil {
  207. return fmt.Errorf("failed to create socket: %w", err)
  208. }
  209. defer syscall.Close(s)
  210. fd := uintptr(s)
  211. var fromNameBytes [unix.IFNAMSIZ]byte
  212. var toNameBytes [unix.IFNAMSIZ]byte
  213. copy(fromNameBytes[:], fromName)
  214. copy(toNameBytes[:], toName)
  215. ifrr := ifreqRename{
  216. Name: fromNameBytes,
  217. Data: uintptr(unsafe.Pointer(&toNameBytes)),
  218. }
  219. // Set the device name using SIOCSIFNAME ioctl
  220. _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
  221. if errno != 0 {
  222. return fmt.Errorf("SIOCSIFNAME ioctl failed: %w", errno)
  223. }
  224. return nil
  225. }