tun_darwin_route_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. //go:build darwin && !ios && !e2e_testing
  2. package overlay
  3. import (
  4. "encoding/binary"
  5. "encoding/hex"
  6. "fmt"
  7. "net"
  8. "net/netip"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "strings"
  13. "testing"
  14. "time"
  15. "github.com/slackhq/nebula/config"
  16. "github.com/slackhq/nebula/routing"
  17. "github.com/slackhq/nebula/test"
  18. "github.com/stretchr/testify/assert"
  19. "github.com/stretchr/testify/require"
  20. netroute "golang.org/x/net/route"
  21. "golang.org/x/sys/unix"
  22. )
  23. // Every mac routes loopback through lo0
  24. func TestRouteInterfaceIn(t *testing.T) {
  25. msgs, err := fetchRoutes()
  26. require.NoError(t, err)
  27. routeInterface := func(s string) int { return routeInterfaceIn(msgs, netip.MustParsePrefix(s)) }
  28. lo0, err := net.InterfaceByName("lo0")
  29. require.NoError(t, err)
  30. assert.Equal(t, lo0.Index, routeInterface("127.0.0.0/8"))
  31. assert.Equal(t, lo0.Index, routeInterface("127.0.0.1/32"), "a host route has no netmask")
  32. assert.Equal(t, lo0.Index, routeInterface("::1/128"))
  33. assert.Equal(t, 0, routeInterface("127.0.0.0/16"), "only an exact prefix counts")
  34. // Most macs also list the default route scoped to their other interfaces
  35. out, err := exec.Command("route", "-n", "get", "default").Output()
  36. require.NoError(t, err)
  37. var name string
  38. for _, line := range strings.Split(string(out), "\n") {
  39. if v, ok := strings.CutPrefix(strings.TrimSpace(line), "interface: "); ok {
  40. name = v
  41. }
  42. }
  43. if name == "" {
  44. t.Skip("no default route")
  45. }
  46. def, err := net.InterfaceByName(name)
  47. require.NoError(t, err)
  48. assert.Equal(t, def.Index, routeInterface("0.0.0.0/0"))
  49. }
  50. func TestRouteInterfaceIn_SkipsScopedCopies(t *testing.T) {
  51. route := func(index, flags int) netroute.Message {
  52. addrs := make([]netroute.Addr, unix.RTAX_MAX)
  53. addrs[unix.RTAX_DST] = &netroute.Inet4Addr{IP: [4]byte{192, 168, 1, 0}}
  54. addrs[unix.RTAX_NETMASK] = &netroute.Inet4Addr{IP: [4]byte{255, 255, 255, 0}}
  55. return &netroute.RouteMessage{Index: index, Flags: flags, Addrs: addrs}
  56. }
  57. p := netip.MustParsePrefix("192.168.1.0/24")
  58. assert.Equal(t, 7, routeInterfaceIn([]netroute.Message{route(4, unix.RTF_UP|unix.RTF_IFSCOPE), route(7, unix.RTF_UP)}, p))
  59. assert.Equal(t, 0, routeInterfaceIn([]netroute.Message{route(4, unix.RTF_UP|unix.RTF_IFSCOPE)}, p))
  60. }
  61. // A reload can happen before Activate
  62. func TestTunRoutesBeforeActivate(t *testing.T) {
  63. tn := &tun{l: test.NewLogger()}
  64. routes := []Route{{
  65. Cidr: netip.MustParsePrefix("10.253.1.0/24"),
  66. Via: routing.Gateways{routing.NewGateway(netip.MustParseAddr("10.250.0.2"), 1)},
  67. Install: true,
  68. }}
  69. tn.Routes.Store(&routes)
  70. assert.NoError(t, tn.addRoutes(true, nil))
  71. assert.NoError(t, tn.removeRoutes(routes, nil))
  72. }
  73. // A tun.routes entry with no via is never installed, so removing it must not delete a route for the same cidr that
  74. // belongs to someone else
  75. func TestTunReloadLeavesRoutesItNeverInstalled(t *testing.T) {
  76. if os.Geteuid() != 0 {
  77. t.Skip("creating a utun and adding routes needs root")
  78. }
  79. lo0, err := net.InterfaceByName("lo0")
  80. require.NoError(t, err)
  81. cidr := "10.250.0.128/25"
  82. require.NoError(t, exec.Command("route", "-q", "-n", "add", "-net", cidr, "-interface", "lo0").Run())
  83. t.Cleanup(func() { _ = exec.Command("route", "-q", "-n", "delete", "-net", cidr).Run() })
  84. l := test.NewLogger()
  85. c := config.NewC(l)
  86. require.NoError(t, c.LoadString("tun:\n routes:\n - route: "+cidr+"\n mtu: 1300\n"))
  87. tn, err := newTun(c, l, []netip.Prefix{netip.MustParsePrefix("10.250.0.1/24")}, false)
  88. require.NoError(t, err)
  89. t.Cleanup(func() { _ = tn.Close() })
  90. require.NoError(t, tn.Activate())
  91. res := c.ReloadConfigString("tun: {}\n")
  92. require.NoError(t, res.Err)
  93. assert.False(t, res.Failed, "removing a route we never installed failed the reload")
  94. msgs, err := fetchRoutes()
  95. require.NoError(t, err)
  96. assert.Equal(t, lo0.Index, routeInterfaceIn(msgs, netip.MustParsePrefix(cidr)))
  97. }
  98. // Captured on macOS 15.6 while adding and removing 198.51.100.77/24 on a feth
  99. const (
  100. // The kernel deleting the address's subnet route. It packs its sockaddrs without padding, x/net/route can't parse it
  101. kernelSubnetDelete = "870005022300000000010002070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010020000c63364000000000000000000141223000600000000000000000000000000000007ffffffffffff"
  102. addressRemoved = "5000050db400000001010000230000000000000007020000ffffff00141223000606060066657468373766657468004d10020000c633644d000000000000000010020000c63364ff0000000000000000"
  103. // With no interface address, which x/net/route skips
  104. interfaceChanged = "7000050e000000004288000023000000060000060e000000dc05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073e5ce6a0000000000000000000001000000000000000000"
  105. )
  106. func TestRoutesMayHaveMoved(t *testing.T) {
  107. cidr := netip.MustParsePrefix("192.168.1.0/24")
  108. via := routing.Gateways{routing.NewGateway(netip.MustParseAddr("10.250.0.2"), 1)}
  109. routes := []Route{{Cidr: cidr, Via: via, Install: true}}
  110. // A request like route(8) sends, the kernel echoes it to every routing socket
  111. request := func(typ, flags int, dst, mask [4]byte) []byte {
  112. m := &netroute.RouteMessage{
  113. Version: unix.RTM_VERSION,
  114. Type: typ,
  115. Flags: flags,
  116. ID: 1234,
  117. Addrs: []netroute.Addr{
  118. unix.RTAX_DST: &netroute.Inet4Addr{IP: dst},
  119. unix.RTAX_GATEWAY: &netroute.LinkAddr{Index: 4},
  120. unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: mask},
  121. },
  122. }
  123. b, err := m.Marshal()
  124. require.NoError(t, err)
  125. return b
  126. }
  127. raw := func(s string) []byte {
  128. b, err := hex.DecodeString(s)
  129. require.NoError(t, err)
  130. return b
  131. }
  132. lan, mask := [4]byte{192, 168, 1, 0}, [4]byte{255, 255, 255, 0}
  133. routesMayHaveMoved := func(b []byte, routes []Route) bool {
  134. moved, _, _ := routesMayHaveMoved(b, routes)
  135. return moved
  136. }
  137. assert.True(t, routesMayHaveMoved(request(unix.RTM_DELETE, unix.RTF_UP, lan, mask), routes))
  138. assert.True(t, routesMayHaveMoved(request(unix.RTM_ADD, unix.RTF_UP, lan, mask), routes))
  139. assert.True(t, routesMayHaveMoved(request(unix.RTM_CHANGE, unix.RTF_UP, lan, mask), routes))
  140. assert.False(t, routesMayHaveMoved(request(unix.RTM_GET, unix.RTF_UP, lan, mask), routes))
  141. assert.False(t, routesMayHaveMoved(request(unix.RTM_DELETE, unix.RTF_UP|unix.RTF_IFSCOPE, lan, mask), routes), "a scoped route never held the prefix")
  142. assert.False(t, routesMayHaveMoved(request(unix.RTM_ADD, unix.RTF_UP|unix.RTF_IFSCOPE, lan, mask), routes), "configd adds a scoped copy for each interface")
  143. assert.False(t, routesMayHaveMoved(request(unix.RTM_DELETE, unix.RTF_UP, lan, [4]byte{255, 255, 0, 0}), routes), "only an exact prefix counts")
  144. assert.False(t, routesMayHaveMoved(request(unix.RTM_DELETE, unix.RTF_UP, lan, mask), []Route{{Cidr: cidr, Install: true}}), "a route with no via is never installed")
  145. assert.False(t, routesMayHaveMoved(request(unix.RTM_DELETE, unix.RTF_UP, lan, mask), []Route{{Cidr: cidr, Via: via}}), "install: false")
  146. failed := request(unix.RTM_DELETE, unix.RTF_UP, lan, mask)
  147. binary.NativeEndian.PutUint32(failed[24:28], uint32(unix.ESRCH))
  148. assert.False(t, routesMayHaveMoved(failed, routes), "a failed request is echoed too")
  149. _, err := netroute.ParseRIB(unix.NET_RT_DUMP, raw(kernelSubnetDelete))
  150. require.Error(t, err, "if x/net/route learns to parse these, match their prefix instead")
  151. assert.True(t, routesMayHaveMoved(raw(kernelSubnetDelete), routes), "the kernel's own deletes could be any prefix")
  152. neighbor := raw(kernelSubnetDelete)
  153. binary.NativeEndian.PutUint32(neighbor[8:12], unix.RTF_UP|unix.RTF_HOST|unix.RTF_LLINFO)
  154. assert.False(t, routesMayHaveMoved(neighbor, routes), "a neighbor expiring")
  155. assert.True(t, routesMayHaveMoved(raw(addressRemoved), routes))
  156. assert.True(t, routesMayHaveMoved(raw(interfaceChanged), routes), "detaching an interface deletes its routes without a message for each")
  157. }
  158. // A request names its prefix and who made it, the kernel's own and interface changes name neither
  159. func TestRoutesMayHaveMovedSaysWho(t *testing.T) {
  160. cidr := netip.MustParsePrefix("192.168.1.0/24")
  161. routes := []Route{{Cidr: cidr, Via: routing.Gateways{routing.NewGateway(netip.MustParseAddr("10.250.0.2"), 1)}, Install: true}}
  162. m := &netroute.RouteMessage{
  163. Version: unix.RTM_VERSION,
  164. Type: unix.RTM_CHANGE,
  165. Flags: unix.RTF_UP,
  166. ID: 1234,
  167. Addrs: []netroute.Addr{
  168. unix.RTAX_DST: &netroute.Inet4Addr{IP: [4]byte{192, 168, 1, 0}},
  169. unix.RTAX_GATEWAY: &netroute.LinkAddr{Index: 4},
  170. unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: [4]byte{255, 255, 255, 0}},
  171. },
  172. }
  173. b, err := m.Marshal()
  174. require.NoError(t, err)
  175. moved, p, pid := routesMayHaveMoved(b, routes)
  176. assert.True(t, moved)
  177. assert.Equal(t, cidr, p)
  178. assert.Equal(t, int32(1234), pid)
  179. for name, s := range map[string]string{"kernel": kernelSubnetDelete, "ifinfo": interfaceChanged} {
  180. b, err := hex.DecodeString(s)
  181. require.NoError(t, err)
  182. moved, p, pid := routesMayHaveMoved(b, routes)
  183. assert.True(t, moved, name)
  184. assert.False(t, p.IsValid(), name)
  185. assert.Zero(t, pid, name)
  186. }
  187. }
  188. func TestProcessName(t *testing.T) {
  189. assert.Equal(t, "launchd", processName(1))
  190. self := filepath.Base(os.Args[0])
  191. assert.Equal(t, self[:min(len(self), 16)], processName(int32(os.Getpid())))
  192. assert.Empty(t, processName(-1))
  193. }
  194. // newRouteTestTun is an activated tun for the root tests below with an unsafe_route for each of cidrs. sweep replaces
  195. // the minute between sweeps when it isn't zero.
  196. func newRouteTestTun(t *testing.T, sweep time.Duration, cidrs ...string) (*tun, *config.C, string) {
  197. requireRoot(t)
  198. cfg := "tun:\n unsafe_routes:\n"
  199. for _, cidr := range cidrs {
  200. via := "10.250.0.2"
  201. if netip.MustParsePrefix(cidr).Addr().Is6() {
  202. via = "fd00:250::2"
  203. }
  204. cfg += " - route: " + cidr + "\n via: " + via + "\n"
  205. }
  206. l := test.NewLogger()
  207. c := config.NewC(l)
  208. require.NoError(t, c.LoadString(cfg))
  209. tn, err := newTun(c, l, []netip.Prefix{netip.MustParsePrefix("10.250.0.1/24"), netip.MustParsePrefix("fd00:250::1/64")}, false)
  210. require.NoError(t, err)
  211. if sweep != 0 {
  212. tn.routeSweepEvery = sweep
  213. }
  214. t.Cleanup(func() { _ = tn.Close() })
  215. require.NoError(t, tn.Activate())
  216. return tn, c, cfg
  217. }
  218. func requireRoot(t *testing.T) {
  219. if os.Geteuid() != 0 {
  220. t.Skip("creating interfaces and adding routes needs root")
  221. }
  222. }
  223. // routeVia is the index of the interface that routes cidr, 0 if none does
  224. func routeVia(t *testing.T, cidr string) int {
  225. msgs, err := fetchRoutes()
  226. require.NoError(t, err)
  227. return routeInterfaceIn(msgs, netip.MustParsePrefix(cidr))
  228. }
  229. // routeStateOf is what tn made of cidr when it last looked
  230. func routeStateOf(tn *tun, cidr string) routeState {
  231. tn.routesMu.Lock()
  232. defer tn.routesMu.Unlock()
  233. return tn.routeStates[netip.MustParsePrefix(cidr)]
  234. }
  235. func routeArgs(action, cidr string, extra ...string) []string {
  236. args := []string{"-q", "-n", action}
  237. if netip.MustParsePrefix(cidr).Addr().Is6() {
  238. args = append(args, "-inet6")
  239. }
  240. return append(append(args, "-net", cidr), extra...)
  241. }
  242. func routeCmd(t *testing.T, action, cidr string, extra ...string) {
  243. requireRouteOK(t, exec.Command("route", routeArgs(action, cidr, extra...)...))
  244. }
  245. // requireRouteOK runs cmd, which runs route(8). route exits 0 when the kernel rejects its request, only what it prints
  246. // says so
  247. func requireRouteOK(t *testing.T, cmd *exec.Cmd) {
  248. out, err := cmd.CombinedOutput()
  249. require.NoError(t, err, string(out))
  250. require.NotContains(t, string(out), "writing to routing socket")
  251. }
  252. // cleanupRoute deletes cidr's route when the test ends, whoever holds it
  253. func cleanupRoute(t *testing.T, cidr string) {
  254. t.Cleanup(func() { _ = exec.Command("route", routeArgs("delete", cidr)...).Run() })
  255. }
  256. // routeThroughLo0 routes cidr through lo0, like a lan nebula started on
  257. func routeThroughLo0(t *testing.T, cidr string) int {
  258. requireRoot(t)
  259. routeCmd(t, "add", cidr, "-interface", "lo0")
  260. cleanupRoute(t, cidr)
  261. lo0, err := net.InterfaceByName("lo0")
  262. require.NoError(t, err)
  263. return lo0.Index
  264. }
  265. // Leaving the lan that held an unsafe_route's prefix must not need a restart to get the unsafe_route
  266. func TestTunAddsRouteOnceItsPrefixIsFree(t *testing.T) {
  267. for _, cidr := range []string{"10.251.0.0/24", "2001:db8:251::/64"} {
  268. t.Run(cidr, func(t *testing.T) {
  269. lo0 := routeThroughLo0(t, cidr)
  270. tn, _, _ := newRouteTestTun(t, 0, cidr)
  271. require.Equal(t, lo0, routeVia(t, cidr), "an existing route keeps its prefix")
  272. routeCmd(t, "delete", cidr)
  273. assert.Eventually(t, func() bool { return routeVia(t, cidr) == tn.linkAddr.Load().Index }, 5*time.Second, 50*time.Millisecond)
  274. })
  275. }
  276. }
  277. // The kernel deletes a route by prefix alone, so dropping an unsafe_route someone else holds must not delete theirs
  278. func TestTunReloadLeavesRouteItYielded(t *testing.T) {
  279. cidr := "10.251.1.0/24"
  280. lo0 := routeThroughLo0(t, cidr)
  281. _, c, _ := newRouteTestTun(t, 0, cidr)
  282. res := c.ReloadConfigString("tun: {}\n")
  283. require.NoError(t, res.Err)
  284. assert.False(t, res.Failed)
  285. assert.Equal(t, lo0, routeVia(t, cidr))
  286. }
  287. // configd takes a prefix by deleting the route holding it and adding its own. Ours goes back once theirs is removed
  288. func TestTunYieldsToARouteThatReplacesIt(t *testing.T) {
  289. cidr := "10.251.3.0/24"
  290. tn, _, _ := newRouteTestTun(t, 0, cidr)
  291. ours := tn.linkAddr.Load().Index
  292. require.Equal(t, ours, routeVia(t, cidr))
  293. cleanupRoute(t, cidr)
  294. requireRouteOK(t, exec.Command("sh", "-c", "route -q -n delete -net "+cidr+" && route -q -n add -net "+cidr+" -interface lo0"))
  295. lo0, err := net.InterfaceByName("lo0")
  296. require.NoError(t, err)
  297. time.Sleep(2 * routeRetryDelay)
  298. require.Equal(t, lo0.Index, routeVia(t, cidr))
  299. routeCmd(t, "delete", cidr)
  300. assert.Eventually(t, func() bool { return routeVia(t, cidr) == ours }, 5*time.Second, 50*time.Millisecond)
  301. }
  302. // A route someone deleted, with nothing in its place, stays gone until a reload, so they can put their own there
  303. func TestTunLeavesRouteDeletedByHand(t *testing.T) {
  304. cidr := "10.251.4.0/24"
  305. tn, c, cfg := newRouteTestTun(t, 200*time.Millisecond, cidr)
  306. ours := tn.linkAddr.Load().Index
  307. require.Equal(t, ours, routeVia(t, cidr))
  308. routeCmd(t, "delete", cidr)
  309. assert.Never(t, func() bool { return routeVia(t, cidr) != 0 }, 3*routeRetryDelay, 50*time.Millisecond)
  310. // A reload with nothing changed is how to ask for it back
  311. res := c.ReloadConfigString(cfg)
  312. require.NoError(t, res.Err)
  313. assert.False(t, res.Failed)
  314. assert.Equal(t, ours, routeVia(t, cidr))
  315. }
  316. // configd deleting ours and adding the lan's more than routeRetryDelay apart, or a sweep landing in between, looks like
  317. // a hand delete. Seeing the lan hold the prefix puts ours back in line, it goes back once theirs is removed
  318. func TestTunYieldsAfterLeavingARouteDeletedByHand(t *testing.T) {
  319. for name, tc := range map[string]struct {
  320. cidr string
  321. sweep time.Duration
  322. misfile func(t *testing.T, tn *tun, cidr string)
  323. }{
  324. // The table went quiet with nothing in its place. The minute sweep leaves noticing the lan's route to the watcher
  325. "settled": {"10.251.6.0/24", 0, func(t *testing.T, tn *tun, cidr string) {
  326. require.Eventually(t, func() bool { return routeStateOf(tn, cidr).kind == routeRemoved }, 5*time.Second, 50*time.Millisecond)
  327. }},
  328. // The sweep that caps a table that never settles
  329. "swept": {"10.251.7.0/24", 200 * time.Millisecond, func(t *testing.T, tn *tun, cidr string) {
  330. tn.reconcileRoutes()
  331. require.Equal(t, routeRemoved, routeStateOf(tn, cidr).kind)
  332. }},
  333. } {
  334. t.Run(name, func(t *testing.T) {
  335. tn, _, _ := newRouteTestTun(t, tc.sweep, tc.cidr)
  336. ours := tn.linkAddr.Load().Index
  337. require.Equal(t, ours, routeVia(t, tc.cidr))
  338. routeCmd(t, "delete", tc.cidr)
  339. tc.misfile(t, tn, tc.cidr)
  340. lo0 := routeThroughLo0(t, tc.cidr)
  341. require.Eventually(t, func() bool {
  342. s := routeStateOf(tn, tc.cidr)
  343. return s.kind == routeYielded && s.holder == lo0
  344. }, 5*time.Second, 50*time.Millisecond)
  345. routeCmd(t, "delete", tc.cidr)
  346. assert.Eventually(t, func() bool { return routeVia(t, tc.cidr) == ours }, 5*time.Second, 50*time.Millisecond)
  347. })
  348. }
  349. }
  350. // Changing our route to another interface hands it the prefix, ours goes back once theirs is removed rather than being
  351. // taken for a hand delete
  352. func TestTunYieldsToARouteChangedAway(t *testing.T) {
  353. for _, cidr := range []string{"10.251.8.0/24", "2001:db8:258::/64"} {
  354. t.Run(cidr, func(t *testing.T) {
  355. tn, _, _ := newRouteTestTun(t, 0, cidr)
  356. ours := tn.linkAddr.Load().Index
  357. require.Equal(t, ours, routeVia(t, cidr))
  358. cleanupRoute(t, cidr)
  359. routeCmd(t, "change", cidr, "-interface", "lo0")
  360. lo0, err := net.InterfaceByName("lo0")
  361. require.NoError(t, err)
  362. require.Equal(t, lo0.Index, routeVia(t, cidr))
  363. require.Eventually(t, func() bool {
  364. s := routeStateOf(tn, cidr)
  365. return s.kind == routeYielded && s.holder == lo0.Index
  366. }, 5*time.Second, 50*time.Millisecond)
  367. routeCmd(t, "delete", cidr)
  368. assert.Eventually(t, func() bool { return routeVia(t, cidr) == ours }, 5*time.Second, 50*time.Millisecond)
  369. })
  370. }
  371. }
  372. // Detaching an interface deletes its routes without a routing socket message for each, like unplugging the dock whose
  373. // ethernet configd routed the lan through
  374. func TestTunAddsRouteWhenItsInterfaceDetaches(t *testing.T) {
  375. requireRoot(t)
  376. var feth string
  377. for i := 100; i < 200 && feth == ""; i++ {
  378. if _, err := net.InterfaceByName(fmt.Sprintf("feth%d", i)); err != nil {
  379. feth = fmt.Sprintf("feth%d", i)
  380. }
  381. }
  382. require.NotEmpty(t, feth)
  383. require.NoError(t, exec.Command("ifconfig", feth, "create").Run())
  384. t.Cleanup(func() { _ = exec.Command("ifconfig", feth, "destroy").Run() })
  385. require.NoError(t, exec.Command("ifconfig", feth, "inet", "198.51.100.5/24", "up").Run())
  386. // Static and cloning with no gateway, like the lan routes configd installs. Named by the feth's address, macOS 27
  387. // refuses one named by the feth itself
  388. cidr := "10.251.2.0/24"
  389. routeCmd(t, "add", cidr, "-interface", "198.51.100.5")
  390. cleanupRoute(t, cidr)
  391. ifc, err := net.InterfaceByName(feth)
  392. require.NoError(t, err)
  393. tn, _, _ := newRouteTestTun(t, 0, cidr)
  394. require.Equal(t, ifc.Index, routeVia(t, cidr))
  395. // Losing the address first leaves the route, the reconcile finds it still held
  396. require.NoError(t, exec.Command("ifconfig", feth, "inet", "198.51.100.5", "-alias").Run())
  397. time.Sleep(2 * routeRetryDelay)
  398. require.Equal(t, ifc.Index, routeVia(t, cidr))
  399. // Only RTM_IFINFO says the interface went, its route goes with no message of its own
  400. require.NoError(t, exec.Command("ifconfig", feth, "destroy").Run())
  401. assert.Eventually(t, func() bool { return routeVia(t, cidr) == tn.linkAddr.Load().Index }, 5*time.Second, 50*time.Millisecond)
  402. }
  403. // The routing socket drops what doesn't fit its buffer without a word, the sweep finds what it missed
  404. func TestTunSweepAddsRouteTheWatcherMissed(t *testing.T) {
  405. cidr := "10.251.5.0/24"
  406. routeThroughLo0(t, cidr)
  407. tn, _, _ := newRouteTestTun(t, 200*time.Millisecond, cidr)
  408. tn.routesMu.Lock()
  409. require.NoError(t, tn.routeWatch.Close())
  410. tn.routesMu.Unlock()
  411. routeCmd(t, "delete", cidr)
  412. assert.Eventually(t, func() bool { return routeVia(t, cidr) == tn.linkAddr.Load().Index }, 5*time.Second, 50*time.Millisecond)
  413. }
  414. // Only naming a different utun asks for a restart. Empty, plain utun and names that aren't utunN take whichever is free
  415. func TestTunDevNeedsRestart(t *testing.T) {
  416. for name, want := range map[string]bool{
  417. "": false,
  418. "utun": false,
  419. "nope": false,
  420. "utun7": false,
  421. "utun3": true,
  422. } {
  423. assert.Equal(t, want, tunDevNeedsRestart(name, "utun7"), name)
  424. }
  425. }