handshake_manager_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. package nebula
  2. import (
  3. "net/netip"
  4. "testing"
  5. "time"
  6. "github.com/gaissmai/bart"
  7. "github.com/slackhq/nebula/cert"
  8. "github.com/slackhq/nebula/header"
  9. "github.com/slackhq/nebula/test"
  10. "github.com/slackhq/nebula/udp"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. )
  14. func Test_NewHandshakeManagerVpnIp(t *testing.T) {
  15. l := test.NewLogger()
  16. localrange := netip.MustParsePrefix("10.1.1.1/24")
  17. ip := netip.MustParseAddr("172.1.1.2")
  18. preferredRanges := []netip.Prefix{localrange}
  19. mainHM := newHostMap(l)
  20. mainHM.preferredRanges.Store(&preferredRanges)
  21. lh := newTestLighthouse()
  22. cs := &CertState{
  23. initiatingVersion: cert.Version1,
  24. privateKey: []byte{},
  25. v1Cert: &dummyCert{version: cert.Version1},
  26. v1Credential: nil,
  27. }
  28. blah := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, defaultHandshakeConfig)
  29. blah.f = &Interface{handshakeManager: blah, pki: &PKI{}, l: l}
  30. blah.f.pki.cs.Store(cs)
  31. now := time.Now()
  32. blah.NextOutboundHandshakeTimerTick(now)
  33. i := blah.StartHandshake(ip, nil)
  34. i2 := blah.StartHandshake(ip, nil)
  35. assert.Same(t, i, i2)
  36. // Adding something to pending should not affect the main hostmap
  37. assert.Empty(t, mainHM.Hosts)
  38. // Confirm they are in the pending index list
  39. assert.Contains(t, blah.vpnIps, ip)
  40. // Jump ahead `HandshakeRetries` ticks, offset by one to get the sleep logic right
  41. for i := 1; i <= DefaultHandshakeRetries+1; i++ {
  42. now = now.Add(time.Duration(i) * DefaultHandshakeTryInterval)
  43. blah.NextOutboundHandshakeTimerTick(now)
  44. }
  45. // Confirm they are still in the pending index list
  46. assert.Contains(t, blah.vpnIps, ip)
  47. // Tick 1 more time, a minute will certainly flush it out
  48. blah.NextOutboundHandshakeTimerTick(now.Add(time.Minute))
  49. // Confirm they have been removed
  50. assert.NotContains(t, blah.vpnIps, ip)
  51. }
  52. // newTestHandshakeManager returns a manager whose handshakes never build a stage 0 packet (the cert has no key), so
  53. // every attempt just bumps the counter and schedules the next one. tick advances its timer wheel by one tryInterval.
  54. func newTestHandshakeManager() (hm *HandshakeManager, tick func()) {
  55. l := test.NewLogger()
  56. mainHM := newHostMap(l)
  57. mainHM.preferredRanges.Store(&[]netip.Prefix{})
  58. hm = NewHandshakeManager(l, mainHM, newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  59. hm.f = &Interface{handshakeManager: hm, pki: &PKI{}, l: l}
  60. hm.f.pki.cs.Store(&CertState{
  61. initiatingVersion: cert.Version1,
  62. privateKey: []byte{},
  63. v1Cert: &dummyCert{version: cert.Version1},
  64. })
  65. now := time.Now()
  66. hm.NextOutboundHandshakeTimerTick(now)
  67. return hm, func() {
  68. now = now.Add(DefaultHandshakeTryInterval)
  69. hm.NextOutboundHandshakeTimerTick(now)
  70. }
  71. }
  72. func Test_HandshakeManager_RestartDropsOldTimer(t *testing.T) {
  73. ip := netip.MustParseAddr("172.1.1.2")
  74. hm, tick := newTestHandshakeManager()
  75. hm.StartHandshake(ip, nil)
  76. old := hm.queryVpnIp(ip)
  77. tick()
  78. tick()
  79. require.Equal(t, int64(1), old.counter)
  80. // Restart the handshake while the old one still has a retry queued
  81. hm.DeleteHostInfo(old.hostinfo)
  82. hm.StartHandshake(ip, nil)
  83. hh := hm.queryVpnIp(ip)
  84. require.NotSame(t, old, hh)
  85. // An entry for timeout d fires d/tryInterval+1 ticks after it is added, so linear backoff lands attempts on ticks
  86. // 2, 4 and 7
  87. var counters []int64
  88. for range 7 {
  89. tick()
  90. counters = append(counters, hh.counter)
  91. }
  92. assert.Equal(t, []int64{0, 1, 1, 2, 2, 2, 3}, counters)
  93. // The old handshake's queued retry ended its chain instead of retrying a deleted handshake
  94. assert.Equal(t, int64(1), old.counter)
  95. assert.Equal(t, 1, testCountTimerWheelEntries(hm.OutboundHandshakeTimer))
  96. }
  97. func Test_HandshakeManager_LighthouseTriggerKeepsOneTimer(t *testing.T) {
  98. ip := netip.MustParseAddr("172.1.1.2")
  99. hm, _ := newTestHandshakeManager()
  100. hm.StartHandshake(ip, nil)
  101. hm.handleOutbound(hm.queryVpnIp(ip), true)
  102. assert.Equal(t, 1, testCountTimerWheelEntries(hm.OutboundHandshakeTimer))
  103. }
  104. func testCountTimerWheelEntries(tw *LockingTimerWheel[*HandshakeHostInfo]) (c int) {
  105. for _, i := range tw.t.wheel {
  106. n := i.Head
  107. for n != nil {
  108. c++
  109. n = n.Next
  110. }
  111. }
  112. return c
  113. }
  114. type mockEncWriter struct {
  115. }
  116. func (mw *mockEncWriter) SendMessageToVpnAddr(_ header.MessageType, _ header.MessageSubType, _ netip.Addr, _, _, _ []byte) {
  117. return
  118. }
  119. func (mw *mockEncWriter) SendVia(via *HostInfo, relay *Relay, ad, nb, out []byte, nocopy bool, q int) {
  120. return
  121. }
  122. func (mw *mockEncWriter) SendMessageToHostInfo(_ header.MessageType, _ header.MessageSubType, _ *HostInfo, _, _, _ []byte) {
  123. return
  124. }
  125. func (mw *mockEncWriter) Handshake(_ netip.Addr) {}
  126. func (mw *mockEncWriter) GetHostInfo(_ netip.Addr) *HostInfo {
  127. return nil
  128. }
  129. func (mw *mockEncWriter) GetCertState() *CertState {
  130. return &CertState{initiatingVersion: cert.Version2}
  131. }
  132. func TestValidatePeerCert(t *testing.T) {
  133. l := test.NewLogger()
  134. myNetwork := netip.MustParsePrefix("10.0.0.1/24")
  135. myAddrTable := new(bart.Lite)
  136. myAddrTable.Insert(netip.PrefixFrom(myNetwork.Addr(), myNetwork.Addr().BitLen()))
  137. myNetTable := new(bart.Lite)
  138. myNetTable.Insert(myNetwork.Masked())
  139. newHM := func() *HandshakeManager {
  140. hm := NewHandshakeManager(l, newHostMap(l), newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  141. hm.f = &Interface{
  142. handshakeManager: hm,
  143. pki: &PKI{},
  144. l: l,
  145. myVpnAddrsTable: myAddrTable,
  146. myVpnNetworksTable: myNetTable,
  147. lightHouse: hm.lightHouse,
  148. }
  149. return hm
  150. }
  151. cached := func(networks ...netip.Prefix) *cert.CachedCertificate {
  152. return &cert.CachedCertificate{
  153. Certificate: &dummyCert{name: "peer", networks: networks},
  154. }
  155. }
  156. via := ViaSender{
  157. UdpAddr: netip.MustParseAddrPort("198.51.100.7:4242"),
  158. IsRelayed: true, // skip the remote allow list (covered separately)
  159. }
  160. t.Run("addr inside our networks sets anyVpnAddrsInCommon", func(t *testing.T) {
  161. hm := newHM()
  162. // 10.0.0.2 falls inside our 10.0.0.0/24
  163. addrs, common, ok := hm.validatePeerCert(via, cached(netip.MustParsePrefix("10.0.0.2/24")))
  164. assert.True(t, ok)
  165. assert.True(t, common)
  166. assert.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.2")}, addrs)
  167. })
  168. t.Run("addr outside our networks leaves anyVpnAddrsInCommon false", func(t *testing.T) {
  169. hm := newHM()
  170. addrs, common, ok := hm.validatePeerCert(via, cached(netip.MustParsePrefix("192.168.1.5/24")))
  171. assert.True(t, ok)
  172. assert.False(t, common)
  173. assert.Equal(t, []netip.Addr{netip.MustParseAddr("192.168.1.5")}, addrs)
  174. })
  175. t.Run("any matching network is enough", func(t *testing.T) {
  176. hm := newHM()
  177. addrs, common, ok := hm.validatePeerCert(via, cached(
  178. netip.MustParsePrefix("192.168.1.5/24"),
  179. netip.MustParsePrefix("10.0.0.42/24"),
  180. ))
  181. assert.True(t, ok)
  182. assert.True(t, common)
  183. assert.Len(t, addrs, 2)
  184. })
  185. t.Run("self-handshake is rejected", func(t *testing.T) {
  186. hm := newHM()
  187. // 10.0.0.1 is in myVpnAddrsTable
  188. addrs, common, ok := hm.validatePeerCert(via, cached(netip.MustParsePrefix("10.0.0.1/24")))
  189. assert.False(t, ok)
  190. assert.False(t, common)
  191. assert.Nil(t, addrs)
  192. })
  193. t.Run("cert with no networks is rejected", func(t *testing.T) {
  194. hm := newHM()
  195. addrs, common, ok := hm.validatePeerCert(via, cached())
  196. assert.False(t, ok)
  197. assert.False(t, common)
  198. assert.Nil(t, addrs)
  199. })
  200. }
  201. func TestHandleIncomingDispatch(t *testing.T) {
  202. l := test.NewLogger()
  203. newHM := func() *HandshakeManager {
  204. hm := NewHandshakeManager(l, newHostMap(l), newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  205. hm.f = &Interface{
  206. handshakeManager: hm,
  207. pki: &PKI{},
  208. l: l,
  209. }
  210. return hm
  211. }
  212. via := ViaSender{
  213. UdpAddr: netip.MustParseAddrPort("198.51.100.7:4242"),
  214. IsRelayed: true, // bypass remote allow list
  215. }
  216. // A packet body of zero length is fine for these tests: dispatch is
  217. // gated on header fields, and we assert that we never reach noise/cert
  218. // processing for any of the malformed shapes here.
  219. pkt := make([]byte, header.Len)
  220. t.Run("unsupported subtype dropped", func(t *testing.T) {
  221. hm := newHM()
  222. h := &header.H{Type: header.Handshake, Subtype: header.MessageSubType(99), MessageCounter: 1}
  223. hm.HandleIncoming(via, pkt, h)
  224. assert.Empty(t, hm.indexes, "no pending handshake should be created")
  225. })
  226. t.Run("stage-1 with non-zero RemoteIndex dropped", func(t *testing.T) {
  227. hm := newHM()
  228. h := &header.H{
  229. Type: header.Handshake,
  230. Subtype: header.HandshakeIXPSK0,
  231. RemoteIndex: 0xdeadbeef,
  232. MessageCounter: 1,
  233. }
  234. hm.HandleIncoming(via, pkt, h)
  235. assert.Empty(t, hm.indexes, "spoofed stage-1 must not create a pending machine")
  236. })
  237. t.Run("continuation with no matching pending index dropped", func(t *testing.T) {
  238. hm := newHM()
  239. h := &header.H{
  240. Type: header.Handshake,
  241. Subtype: header.HandshakeIXPSK0,
  242. RemoteIndex: 0xcafef00d,
  243. MessageCounter: 2,
  244. }
  245. hm.HandleIncoming(via, pkt, h)
  246. assert.Empty(t, hm.indexes, "orphan stage-2 must not create state")
  247. })
  248. }
  249. // Test_PendingHandshakeHasRemotes covers create-tunnel -address: a pending hostinfo must already
  250. // carry its RemoteList when StartHandshake runs its callback, so SetRemote there, before the
  251. // handshake loop has run, is safe and the address becomes one the handshake is sent to.
  252. func Test_PendingHandshakeHasRemotes(t *testing.T) {
  253. l := test.NewLogger()
  254. mainHM := newHostMap(l)
  255. preferredRanges := []netip.Prefix{}
  256. mainHM.preferredRanges.Store(&preferredRanges)
  257. hm := NewHandshakeManager(l, mainHM, newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  258. vpnIp := netip.MustParseAddr("172.1.1.2")
  259. remote := netip.MustParseAddrPort("192.168.1.9:4242")
  260. hi := hm.StartHandshake(vpnIp, func(hh *HandshakeHostInfo) {
  261. require.NotNil(t, hh.hostinfo.remotes, "a pending handshake must never be without its RemoteList")
  262. hh.hostinfo.SetRemote(remote)
  263. })
  264. assert.Contains(t, hi.remotes.CopyAddrs(preferredRanges), remote, "the handshake must be sent to the supplied remote")
  265. assert.Equal(t, remote, hi.GetRemote())
  266. // A RemoteList keeps one learned address per host, so a second remote replaces the
  267. // first — the same behaviour change-remote has on an established tunnel.
  268. other := netip.MustParseAddrPort("192.168.1.10:4242")
  269. hi.SetRemote(other)
  270. assert.Equal(t, []netip.AddrPort{other}, hi.remotes.CopyAddrs(preferredRanges))
  271. assert.Equal(t, other, hi.GetRemote())
  272. }
  273. // newRemotesRaceFixture builds a handshake manager with an established tunnel to vpnAddr whose
  274. // RemoteList is the lighthouse's cached list, the state every teardown-during-handshake race starts from.
  275. func newRemotesRaceFixture(t *testing.T, vpnAddr netip.Addr) (*HandshakeManager, *Interface, *HostInfo) {
  276. t.Helper()
  277. l := test.NewLogger()
  278. mainHM := newHostMap(l)
  279. preferredRanges := []netip.Prefix{}
  280. mainHM.preferredRanges.Store(&preferredRanges)
  281. lh := newTestLighthouse()
  282. hm := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, defaultHandshakeConfig)
  283. f := &Interface{hostMap: mainHM, lightHouse: lh, handshakeManager: hm, l: l}
  284. hm.f = f
  285. established := &HostInfo{
  286. vpnAddrs: []netip.Addr{vpnAddr},
  287. localIndexId: 1,
  288. remoteIndexId: 101,
  289. remotes: lh.QueryCache([]netip.Addr{vpnAddr}),
  290. HandshakePacket: map[uint8][]byte{},
  291. }
  292. mainHM.Lock()
  293. mainHM.unlockedAddHostInfo(established, f)
  294. mainHM.Unlock()
  295. return hm, f, established
  296. }
  297. // With no handshake pending, tearing down the last tunnel still evicts the list.
  298. func Test_CloseTunnelEvictsWithoutPendingHandshake(t *testing.T) {
  299. vpnAddr := netip.MustParseAddr("172.1.1.2")
  300. _, f, established := newRemotesRaceFixture(t, vpnAddr)
  301. f.closeTunnel(established)
  302. assert.NotContains(t, f.hostMap.Hosts, vpnAddr)
  303. assert.NotContains(t, f.lightHouse.addrMap, vpnAddr)
  304. }