connection_state_test.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package nebula
  2. import (
  3. "net/netip"
  4. "testing"
  5. "time"
  6. "github.com/flynn/noise"
  7. "github.com/rcrowley/go-metrics"
  8. "github.com/slackhq/nebula/cert"
  9. ct "github.com/slackhq/nebula/cert_test"
  10. "github.com/slackhq/nebula/handshake"
  11. "github.com/slackhq/nebula/header"
  12. "github.com/slackhq/nebula/test"
  13. "github.com/slackhq/nebula/udp"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/stretchr/testify/require"
  16. )
  17. // runTestHandshake runs a complete IX handshake between two freshly-built
  18. // peers and returns the initiator and responder Results. Used to produce
  19. // real cipher states for tests that need to exercise post-handshake glue.
  20. func runTestHandshake(t *testing.T) (initR, respR *handshake.Result) {
  21. t.Helper()
  22. ca, _, caKey, _ := ct.NewTestCaCert(
  23. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  24. )
  25. caPool := ct.NewTestCAPool(ca)
  26. makeCreds := func(name string, networks []netip.Prefix) handshake.GetCredentialFunc {
  27. c, _, rawKey, _ := ct.NewTestCert(
  28. cert.Version2, cert.Curve_CURVE25519, ca, caKey,
  29. name, ca.NotBefore(), ca.NotAfter(), networks, nil, nil,
  30. )
  31. priv, _, _, err := cert.UnmarshalPrivateKeyFromPEM(rawKey)
  32. require.NoError(t, err)
  33. hsBytes, err := c.MarshalForHandshakes()
  34. require.NoError(t, err)
  35. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  36. cred := handshake.NewCredential(c, hsBytes, priv, ncs)
  37. return func(v cert.Version) *handshake.Credential {
  38. if v == cert.Version2 {
  39. return cred
  40. }
  41. return nil
  42. }
  43. }
  44. verifier := func(c cert.Certificate) (*cert.CachedCertificate, error) {
  45. return caPool.VerifyCertificate(time.Now(), c)
  46. }
  47. initCreds := makeCreds("initiator", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  48. respCreds := makeCreds("responder", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  49. initM, err := handshake.NewMachine(
  50. cert.Version2, initCreds, verifier,
  51. func() (uint32, error) { return 1000, nil },
  52. true, header.HandshakeIXPSK0,
  53. nil,
  54. )
  55. require.NoError(t, err)
  56. respM, err := handshake.NewMachine(
  57. cert.Version2, respCreds, verifier,
  58. func() (uint32, error) { return 2000, nil },
  59. false, header.HandshakeIXPSK0,
  60. nil,
  61. )
  62. require.NoError(t, err)
  63. msg1, err := initM.Initiate(nil)
  64. require.NoError(t, err)
  65. resp, respR, err := respM.ProcessPacket(nil, msg1)
  66. require.NoError(t, err)
  67. require.NotNil(t, respR)
  68. _, initR, err = initM.ProcessPacket(nil, resp)
  69. require.NoError(t, err)
  70. require.NotNil(t, initR)
  71. return initR, respR
  72. }
  73. func TestConnectionState_NextMessageCounter(t *testing.T) {
  74. cs := &ConnectionState{}
  75. cs.messageCounter.Store(RejectAfterMessages - 2)
  76. c, ok := cs.NextMessageCounter()
  77. assert.True(t, ok)
  78. assert.Equal(t, RejectAfterMessages-1, c)
  79. // Hitting the limit refuses and pins the counter there
  80. c, ok = cs.NextMessageCounter()
  81. assert.False(t, ok)
  82. assert.Equal(t, RejectAfterMessages, c)
  83. assert.Equal(t, RejectAfterMessages, cs.messageCounter.Load())
  84. // Continued send attempts stay refused and the counter never wraps
  85. for i := 0; i < 10; i++ {
  86. _, ok = cs.NextMessageCounter()
  87. assert.False(t, ok)
  88. }
  89. assert.Equal(t, RejectAfterMessages, cs.messageCounter.Load())
  90. }
  91. // TestSendNoMetricsDropsExhausted drives the send path to the exhausted drop; metric and out flag prove it.
  92. func TestSendNoMetricsDropsExhausted(t *testing.T) {
  93. initR, _ := runTestHandshake(t)
  94. ci, err := newConnectionStateFromResult(initR)
  95. require.NoError(t, err)
  96. ci.messageCounter.Store(RejectAfterMessages - 1)
  97. f := &Interface{l: test.NewLogger(), messageMetrics: &MessageMetrics{txExhausted: metrics.NewCounter()}}
  98. hostinfo := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, ConnectionState: ci}
  99. f.sendNoMetrics(header.Message, 0, ci, hostinfo, netip.AddrPort{}, []byte{}, make([]byte, 12), make([]byte, mtu), 0)
  100. // The crossing send is refused: it records an exhaustion drop and never reaches connectionManager.Out.
  101. assert.Equal(t, int64(1), f.messageMetrics.txExhausted.Count())
  102. assert.False(t, hostinfo.sentSinceCheck())
  103. }
  104. // TestSendNoMetricsCloseTunnelKeepsRebindEpoch pins that a closing tunnel does not consume a rebind, a later
  105. // packet on a re-established tunnel still needs that edge to trigger the far-side punch.
  106. func TestSendNoMetricsCloseTunnelKeepsRebindEpoch(t *testing.T) {
  107. initR, _ := runTestHandshake(t)
  108. ci, err := newConnectionStateFromResult(initR)
  109. require.NoError(t, err)
  110. f := &Interface{
  111. l: test.NewLogger(),
  112. messageMetrics: &MessageMetrics{txExhausted: metrics.NewCounter()},
  113. writers: []udp.Conn{udp.NoopConn{}},
  114. connectionManager: &connectionManager{},
  115. }
  116. hostinfo := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, ConnectionState: ci}
  117. // Tunnel is on epoch 0, then we rebind.
  118. hostinfo.markOut(0)
  119. f.rebindEpoch.Add(1)
  120. remote := netip.MustParseAddrPort("10.0.0.2:4242")
  121. f.sendNoMetrics(header.CloseTunnel, 0, ci, hostinfo, remote, []byte{}, make([]byte, 12), make([]byte, mtu), 0)
  122. // markOut at the new epoch still reports the move, so the edge was preserved.
  123. assert.True(t, hostinfo.markOut(1), "a CloseTunnel send must not consume the rebind epoch")
  124. }
  125. func TestNewConnectionStateFromResult(t *testing.T) {
  126. initR, respR := runTestHandshake(t)
  127. t.Run("initiator", func(t *testing.T) {
  128. ci, err := newConnectionStateFromResult(initR)
  129. require.NoError(t, err)
  130. assert.True(t, ci.initiator)
  131. assert.Equal(t, initR.MyCert, ci.myCert)
  132. assert.Equal(t, initR.RemoteCert, ci.peerCert)
  133. assert.NotNil(t, ci.eKey)
  134. assert.NotNil(t, ci.dKey)
  135. // IX has 2 handshake messages; the next data-plane send is counter=3.
  136. assert.Equal(t, uint64(2), ci.messageCounter.Load(),
  137. "messageCounter must equal Result.MessageIndex so the next send is N+1")
  138. // Both handshake counters must be marked seen so they don't appear lost.
  139. // Check returns false if an index has already been recorded.
  140. assert.False(t, ci.window.Check(nil, 1), "counter 1 must already be seen")
  141. assert.False(t, ci.window.Check(nil, 2), "counter 2 must already be seen")
  142. // Counter 3 is the next data-plane message and must NOT be pre-marked.
  143. assert.True(t, ci.window.Check(nil, 3), "counter 3 must not be pre-seeded")
  144. })
  145. t.Run("message index too large is refused", func(t *testing.T) {
  146. bad := *initR
  147. bad.MessageIndex = ReplayWindow
  148. ci, err := newConnectionStateFromResult(&bad)
  149. require.Error(t, err)
  150. assert.Nil(t, ci)
  151. })
  152. t.Run("responder", func(t *testing.T) {
  153. ci, err := newConnectionStateFromResult(respR)
  154. require.NoError(t, err)
  155. assert.False(t, ci.initiator)
  156. assert.Equal(t, respR.MyCert, ci.myCert)
  157. assert.Equal(t, respR.RemoteCert, ci.peerCert)
  158. assert.NotNil(t, ci.eKey)
  159. assert.NotNil(t, ci.dKey)
  160. assert.Equal(t, uint64(2), ci.messageCounter.Load())
  161. })
  162. }