tun_reload_linux_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //go:build !e2e_testing
  2. package overlay
  3. import (
  4. "net/netip"
  5. "os"
  6. "testing"
  7. "github.com/DefinedNet/netlink"
  8. "github.com/slackhq/nebula/config"
  9. "github.com/slackhq/nebula/routing"
  10. "github.com/slackhq/nebula/test"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. "golang.org/x/sys/unix"
  14. )
  15. // Only tun.mtu changes. The system calls fail without a real device, what matters is the routes nebula keeps
  16. func TestReloadMTUOnlyKeepsRoutes(t *testing.T) {
  17. routes := " unsafe_routes:\n - route: 192.168.100.0/24\n via: 10.1.0.1\n"
  18. c := config.NewC(test.NewLogger())
  19. require.NoError(t, c.LoadString("tun:\n mtu: 1300\n"+routes))
  20. tn := &tun{
  21. l: test.NewLogger(),
  22. vpnNetworks: []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")},
  23. ioctlFd: ^uintptr(0),
  24. routesFromSystem: map[netip.Prefix]routing.Gateways{},
  25. }
  26. require.NoError(t, tn.reload(c, true, nil))
  27. require.NoError(t, c.ReloadConfigString("tun:\n mtu: 1400\n"+routes).Err)
  28. _ = tn.reload(c, false, nil)
  29. got := *tn.Routes.Load()
  30. require.Len(t, got, 1)
  31. assert.Equal(t, netip.MustParsePrefix("192.168.100.0/24"), got[0].Cidr)
  32. assert.Equal(t, 1400, got[0].MTU)
  33. assert.Equal(t, 1400, tn.DefaultMTU)
  34. }
  35. // The device mtu has to fit the largest route mtu, wherever it sits in the list
  36. func TestReloadMaxMTUIsTheLargestRoute(t *testing.T) {
  37. c := config.NewC(test.NewLogger())
  38. require.NoError(t, c.LoadString("tun:\n mtu: 1300\n unsafe_routes:\n"+
  39. " - route: 192.168.100.0/24\n via: 10.1.0.1\n mtu: 1500\n"+
  40. " - route: 192.168.101.0/24\n via: 10.1.0.1\n mtu: 1400\n"))
  41. tn := &tun{
  42. l: test.NewLogger(),
  43. vpnNetworks: []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")},
  44. ioctlFd: ^uintptr(0),
  45. routesFromSystem: map[netip.Prefix]routing.Gateways{},
  46. }
  47. require.NoError(t, tn.reload(c, true, nil))
  48. assert.Equal(t, 1500, tn.MaxMTU)
  49. assert.Equal(t, 1300, tn.DefaultMTU)
  50. }
  51. // There is no device behind this tun, so adding a route always fails. Removing one is refused without root, as root the
  52. // kernel answers that the route isn't there, which is what we wanted
  53. func TestReloadRouteFailures(t *testing.T) {
  54. route := "tun:\n mtu: 1300\n unsafe_routes:\n - route: 192.168.100.0/24\n via: 10.1.0.1\n"
  55. cases := []struct {
  56. name string
  57. from, to string
  58. wantFails bool
  59. }{
  60. {"added", "tun:\n mtu: 1300\n", route, true},
  61. {"removed", route, "tun:\n mtu: 1300\n", os.Geteuid() != 0},
  62. {"unchanged", route, route + "punchy:\n punch: true\n", false},
  63. }
  64. for _, tc := range cases {
  65. t.Run(tc.name, func(t *testing.T) {
  66. c := config.NewC(test.NewLogger())
  67. require.NoError(t, c.LoadString(tc.from))
  68. tn := &tun{
  69. l: test.NewLogger(),
  70. vpnNetworks: []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")},
  71. ioctlFd: 0, // not activated, the mtu is not checked
  72. routesFromSystem: map[netip.Prefix]routing.Gateways{},
  73. }
  74. require.NoError(t, tn.reload(c, true, nil))
  75. c.RegisterReloadCallback(func(c *config.C, r *config.Reload) {
  76. require.NoError(t, tn.reload(c, false, r))
  77. })
  78. res := c.ReloadConfigString(tc.to)
  79. require.NoError(t, res.Err)
  80. assert.Equal(t, tc.wantFails, res.Failed)
  81. })
  82. }
  83. }
  84. // Keys only read when the device is created need a restart to apply
  85. func TestTunReloadRestartKeys(t *testing.T) {
  86. c := config.NewC(test.NewLogger())
  87. require.NoError(t, c.LoadString("tun:\n dev: nebrk0\n"))
  88. tn, err := NewDeviceFromConfig(c, test.NewLogger(), []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")}, 1)
  89. if err != nil {
  90. t.Skipf("can't create a tun device here: %v", err)
  91. }
  92. t.Cleanup(func() { _ = tn.Close() })
  93. res := c.ReloadConfigString("tun:\n disabled: true\n dev: nebrk1\n tx_queue: 100\n")
  94. require.NoError(t, res.Err)
  95. assert.ElementsMatch(t, []string{"tun.disabled", "tun.dev", "tun.tx_queue"}, res.RestartKeys)
  96. }
  97. // There is no device behind this tun, so it refuses every mtu. Repeating the reload tries again rather than reporting
  98. // it done
  99. func TestReloadRefusedMaxMTURetries(t *testing.T) {
  100. c := config.NewC(test.NewLogger())
  101. require.NoError(t, c.LoadString("tun:\n mtu: 1300\n"))
  102. tn := &tun{
  103. l: test.NewLogger(),
  104. vpnNetworks: []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")},
  105. ioctlFd: ^uintptr(0),
  106. routesFromSystem: map[netip.Prefix]routing.Gateways{},
  107. }
  108. require.NoError(t, tn.reload(c, true, nil))
  109. c.RegisterReloadCallback(func(c *config.C, r *config.Reload) {
  110. require.NoError(t, tn.reload(c, false, r))
  111. })
  112. for range 2 {
  113. res := c.ReloadConfigString("tun:\n mtu: 1400\n")
  114. require.NoError(t, res.Err)
  115. assert.True(t, res.Failed)
  116. }
  117. }
  118. // A reload puts the configured mtu back on the device and our network's route when they were changed outside nebula
  119. func TestTunReloadRestoresMTU(t *testing.T) {
  120. c := config.NewC(test.NewLogger())
  121. require.NoError(t, c.LoadString("tun:\n dev: nebmtu0\n mtu: 1300\n"))
  122. tn, err := newTun(c, test.NewLogger(), []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")}, false)
  123. if err != nil {
  124. t.Skipf("can't create a tun device here: %v", err)
  125. }
  126. t.Cleanup(func() { _ = tn.Close() })
  127. require.NoError(t, tn.Activate())
  128. link, err := netlink.LinkByName(tn.Device)
  129. require.NoError(t, err)
  130. require.NoError(t, netlink.LinkSetMTU(link, 1280))
  131. network := &netlink.Route{LinkIndex: tn.deviceIndex, Table: unix.RT_TABLE_MAIN}
  132. routeMTU := func() int {
  133. routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, network, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE)
  134. require.NoError(t, err)
  135. for _, r := range routes {
  136. if r.Dst != nil && r.Dst.String() == "10.1.0.0/24" {
  137. return r.MTU
  138. }
  139. }
  140. t.Fatal("no route for our network")
  141. return 0
  142. }
  143. require.Equal(t, 1300, routeMTU())
  144. setRouteMTU := func(mtu int) {
  145. routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, network, netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE)
  146. require.NoError(t, err)
  147. for _, r := range routes {
  148. if r.Dst != nil && r.Dst.String() == "10.1.0.0/24" {
  149. r.MTU = mtu
  150. require.NoError(t, netlink.RouteReplace(&r))
  151. }
  152. }
  153. require.Equal(t, mtu, routeMTU())
  154. }
  155. reload := func() {
  156. res := c.ReloadConfigString("tun:\n dev: nebmtu0\n mtu: 1300\n")
  157. require.NoError(t, res.Err)
  158. assert.False(t, res.Failed)
  159. }
  160. setRouteMTU(1280)
  161. reload()
  162. mtu, err := tn.deviceMTU()
  163. require.NoError(t, err)
  164. assert.Equal(t, 1300, mtu)
  165. assert.Equal(t, 1300, routeMTU())
  166. // Only the route this time
  167. setRouteMTU(1280)
  168. reload()
  169. assert.Equal(t, 1300, routeMTU())
  170. }