control_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package nebula
  2. import (
  3. "bytes"
  4. "log/slog"
  5. "net"
  6. "net/netip"
  7. "reflect"
  8. "testing"
  9. "github.com/slackhq/nebula/cert"
  10. "github.com/slackhq/nebula/test"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. )
  14. func TestControl_GetHostInfoByVpnIp(t *testing.T) {
  15. //TODO: CERT-V2 with multiple certificate versions we have a problem with this test
  16. // Some certs versions have different characteristics and each version implements their own Copy() func
  17. // which means this is not a good place to test for exposing memory
  18. l := test.NewLogger()
  19. // Special care must be taken to re-use all objects provided to the hostmap and certificate in the expectedInfo object
  20. // To properly ensure we are not exposing core memory to the caller
  21. hm := newHostMap(l)
  22. hm.preferredRanges.Store(&[]netip.Prefix{})
  23. remote1 := netip.MustParseAddrPort("0.0.0.100:4444")
  24. remote2 := netip.MustParseAddrPort("[1:2:3:4:5:6:7:8]:4444")
  25. ipNet := net.IPNet{
  26. IP: remote1.Addr().AsSlice(),
  27. Mask: net.IPMask{255, 255, 255, 0},
  28. }
  29. ipNet2 := net.IPNet{
  30. IP: remote2.Addr().AsSlice(),
  31. Mask: net.IPMask{255, 255, 255, 0},
  32. }
  33. remotes := NewRemoteList([]netip.Addr{netip.IPv4Unspecified()}, nil)
  34. remotes.unlockedPrependV4(netip.IPv4Unspecified(), netAddrToProtoV4AddrPort(remote1.Addr(), remote1.Port()))
  35. remotes.unlockedPrependV6(netip.IPv4Unspecified(), netAddrToProtoV6AddrPort(remote2.Addr(), remote2.Port()))
  36. vpnIp, ok := netip.AddrFromSlice(ipNet.IP)
  37. assert.True(t, ok)
  38. crt := &dummyCert{}
  39. hi := &HostInfo{
  40. remotes: remotes,
  41. ConnectionState: &ConnectionState{
  42. peerCert: &cert.CachedCertificate{Certificate: crt},
  43. },
  44. remoteIndexId: 200,
  45. localIndexId: 201,
  46. vpnAddrs: []netip.Addr{vpnIp},
  47. relayState: RelayState{
  48. relays: nil,
  49. relayForByAddr: map[netip.Addr]*Relay{},
  50. relayForByIdx: map[uint32]*Relay{},
  51. },
  52. }
  53. hi.remote.Store(&remote1)
  54. hm.unlockedAddHostInfo(hi, &Interface{})
  55. vpnIp2, ok := netip.AddrFromSlice(ipNet2.IP)
  56. assert.True(t, ok)
  57. hi2 := &HostInfo{
  58. remotes: remotes,
  59. ConnectionState: &ConnectionState{
  60. peerCert: nil,
  61. },
  62. remoteIndexId: 200,
  63. localIndexId: 201,
  64. vpnAddrs: []netip.Addr{vpnIp2},
  65. relayState: RelayState{
  66. relays: nil,
  67. relayForByAddr: map[netip.Addr]*Relay{},
  68. relayForByIdx: map[uint32]*Relay{},
  69. },
  70. }
  71. hi2.remote.Store(&remote1)
  72. hm.unlockedAddHostInfo(hi2, &Interface{})
  73. c := Control{
  74. state: StateReady,
  75. f: &Interface{
  76. hostMap: hm,
  77. },
  78. l: test.NewLogger(),
  79. }
  80. thi := c.GetHostInfoByVpnAddr(vpnIp, false)
  81. expectedInfo := ControlHostInfo{
  82. VpnAddrs: []netip.Addr{vpnIp},
  83. LocalIndex: 201,
  84. RemoteIndex: 200,
  85. RemoteAddrs: []netip.AddrPort{remote2, remote1},
  86. Cert: crt.Copy(),
  87. MessageCounter: 0,
  88. CurrentRemote: remote1,
  89. CurrentRelaysToMe: []netip.Addr{},
  90. CurrentRelaysThroughMe: []netip.Addr{},
  91. }
  92. // Make sure we don't have any unexpected fields
  93. assertFields(t, []string{"VpnAddrs", "LocalIndex", "RemoteIndex", "RemoteAddrs", "Cert", "MessageCounter", "CurrentRemote", "CurrentRelaysToMe", "CurrentRelaysThroughMe"}, thi)
  94. assert.Equal(t, &expectedInfo, thi)
  95. test.AssertDeepCopyEqual(t, &expectedInfo, thi)
  96. // Make sure we don't panic if the host info doesn't have a cert yet
  97. assert.NotPanics(t, func() {
  98. thi = c.GetHostInfoByVpnAddr(vpnIp2, false)
  99. })
  100. }
  101. func assertFields(t *testing.T, expected []string, actualStruct any) {
  102. val := reflect.ValueOf(actualStruct).Elem()
  103. fields := make([]string, val.NumField())
  104. for i := 0; i < val.NumField(); i++ {
  105. fields[i] = val.Type().Field(i).Name
  106. }
  107. assert.Equal(t, expected, fields)
  108. }
  109. // alwaysAllowV4/V6 are check funcs that accept every entry (including nil pointers),
  110. // letting us inject a nil *V4AddrPort/*V6AddrPort into a RemoteList's reported cache
  111. // the same way a malformed proto message off the wire could.
  112. func alwaysAllowV4(netip.Addr, *V4AddrPort) bool { return true }
  113. func alwaysAllowV6(netip.Addr, *V6AddrPort) bool { return true }
  114. // TestGetRelays_SkipsNilRelayAddrs proves GetRelays tolerates nil entries in the
  115. // RelayVpnAddrs proto slice (which protoAddrToNetAddr would nil-deref on) and still
  116. // returns the valid relays, including the legacy OldRelayVpnAddrs.
  117. func TestGetRelays_SkipsNilRelayAddrs(t *testing.T) {
  118. good := netip.MustParseAddr("10.0.0.9")
  119. d := &NebulaMetaDetails{
  120. OldRelayVpnAddrs: []uint32{0x0a000001}, // 10.0.0.1
  121. RelayVpnAddrs: []*Addr{
  122. nil,
  123. netAddrToProtoAddr(good),
  124. nil,
  125. },
  126. }
  127. var relays []netip.Addr
  128. require.NotPanics(t, func() { relays = d.GetRelays() })
  129. assert.Equal(t, []netip.Addr{
  130. netip.MustParseAddr("10.0.0.1"),
  131. good,
  132. }, relays)
  133. }
  134. // TestGetRelays_AllNil ensures an all-nil RelayVpnAddrs slice yields no relays and no panic.
  135. func TestGetRelays_AllNil(t *testing.T) {
  136. d := &NebulaMetaDetails{RelayVpnAddrs: []*Addr{nil, nil}}
  137. var relays []netip.Addr
  138. require.NotPanics(t, func() { relays = d.GetRelays() })
  139. assert.Empty(t, relays)
  140. }
  141. // TestRemoteList_CopyCache_SkipsNilReported proves CopyCache skips nil reported
  142. // pointers (v4 and v6) instead of nil-dereferencing them in protoV*AddrPortToNetAddrPort.
  143. func TestRemoteList_CopyCache_SkipsNilReported(t *testing.T) {
  144. owner := netip.MustParseAddr("10.0.0.1")
  145. rl := NewRemoteList([]netip.Addr{owner}, nil)
  146. rl.unlockedSetV4(owner, owner, []*V4AddrPort{
  147. nil,
  148. newIp4AndPortFromString("1.2.3.4:5"),
  149. nil,
  150. }, alwaysAllowV4)
  151. rl.unlockedSetV6(owner, owner, []*V6AddrPort{
  152. nil,
  153. newIp6AndPortFromString("[1::1]:6"),
  154. nil,
  155. }, alwaysAllowV6)
  156. var cm *CacheMap
  157. require.NotPanics(t, func() { cm = rl.CopyCache() })
  158. c := (*cm)[owner.String()]
  159. require.NotNil(t, c)
  160. assert.ElementsMatch(t, []netip.AddrPort{
  161. netip.MustParseAddrPort("1.2.3.4:5"),
  162. netip.MustParseAddrPort("[1::1]:6"),
  163. }, c.Reported)
  164. }
  165. // TestRemoteList_Rebuild_SkipsNilReported drives unlockedCollect (via Rebuild) with
  166. // nil reported entries and confirms only the valid addresses survive, with no panic.
  167. func TestRemoteList_Rebuild_SkipsNilReported(t *testing.T) {
  168. owner := netip.MustParseAddr("10.0.0.1")
  169. rl := NewRemoteList([]netip.Addr{owner}, nil)
  170. rl.unlockedSetV4(owner, owner, []*V4AddrPort{
  171. nil,
  172. newIp4AndPortFromString("1.2.3.4:5"),
  173. }, alwaysAllowV4)
  174. rl.unlockedSetV6(owner, owner, []*V6AddrPort{
  175. newIp6AndPortFromString("[1::1]:6"),
  176. nil,
  177. }, alwaysAllowV6)
  178. require.NotPanics(t, func() { rl.Rebuild([]netip.Prefix{}) })
  179. assert.ElementsMatch(t, []netip.AddrPort{
  180. netip.MustParseAddrPort("1.2.3.4:5"),
  181. netip.MustParseAddrPort("[1::1]:6"),
  182. }, rl.addrs)
  183. }
  184. // newRelayControl marshals a NebulaControl the way it arrives on the wire so we can feed
  185. // it through HandleControlMsg's unmarshal + validate path.
  186. func newRelayControl(t *testing.T, typ NebulaControl_MessageType, from, to *Addr) []byte {
  187. t.Helper()
  188. msg := &NebulaControl{
  189. Type: typ,
  190. RelayFromAddr: from,
  191. RelayToAddr: to,
  192. }
  193. b, err := msg.Marshal()
  194. require.NoError(t, err)
  195. return b
  196. }
  197. // TestRelayManager_HandleControlMsg_NilRelayAddrs verifies the validation block added to
  198. // HandleControlMsg: CreateRelay{Request,Response} carrying a nil RelayFromAddr or
  199. // RelayToAddr are dropped with a debug log rather than nil-dereferencing downstream.
  200. func TestRelayManager_HandleControlMsg_NilRelayAddrs(t *testing.T) {
  201. good := netAddrToProtoAddr(netip.MustParseAddr("10.0.0.9"))
  202. cases := []struct {
  203. name string
  204. typ NebulaControl_MessageType
  205. from *Addr
  206. to *Addr
  207. wantLog string // debug substring expected, "" == expect no drop log
  208. }{
  209. {"request nil from", NebulaControl_CreateRelayRequest, nil, good, "nil RelayFromAddr"},
  210. {"request nil to", NebulaControl_CreateRelayRequest, good, nil, "nil RelayToAddr"},
  211. {"request both nil", NebulaControl_CreateRelayRequest, nil, nil, "nil RelayFromAddr"},
  212. {"response nil from", NebulaControl_CreateRelayResponse, nil, good, "nil RelayFromAddr"},
  213. {"response nil to", NebulaControl_CreateRelayResponse, good, nil, "nil RelayToAddr"},
  214. // A non-relay control type is not subject to the relay-addr validation and must
  215. // pass through it untouched (the final switch simply no-ops on it).
  216. {"unrelated type nil addrs", NebulaControl_None, nil, nil, ""},
  217. }
  218. for _, tc := range cases {
  219. t.Run(tc.name, func(t *testing.T) {
  220. var buf bytes.Buffer
  221. l := test.NewLoggerWithOutputAndLevel(&buf, slog.LevelDebug)
  222. rm := &relayManager{l: l, hostmap: newHostMap(l)}
  223. rm.useRelays.Store(true)
  224. f := &Interface{l: l}
  225. h := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.2")}, localIndexId: 1}
  226. d := newRelayControl(t, tc.typ, tc.from, tc.to)
  227. require.NotPanics(t, func() { rm.HandleControlMsg(h, d, f) })
  228. if tc.wantLog == "" {
  229. assert.NotContains(t, buf.String(), "nil Relay")
  230. } else {
  231. assert.Contains(t, buf.String(), tc.wantLog)
  232. }
  233. })
  234. }
  235. }