mirror of
https://github.com/slackhq/nebula.git
synced 2026-09-30 10:26:39 +02:00
166 lines
5.5 KiB
Go
166 lines
5.5 KiB
Go
package nebula
|
|
|
|
import (
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/flynn/noise"
|
|
"github.com/rcrowley/go-metrics"
|
|
"github.com/slackhq/nebula/cert"
|
|
ct "github.com/slackhq/nebula/cert_test"
|
|
"github.com/slackhq/nebula/handshake"
|
|
"github.com/slackhq/nebula/header"
|
|
"github.com/slackhq/nebula/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// runTestHandshake runs a complete IX handshake between two freshly-built
|
|
// peers and returns the initiator and responder Results. Used to produce
|
|
// real cipher states for tests that need to exercise post-handshake glue.
|
|
func runTestHandshake(t *testing.T) (initR, respR *handshake.Result) {
|
|
t.Helper()
|
|
|
|
ca, _, caKey, _ := ct.NewTestCaCert(
|
|
cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
|
|
)
|
|
caPool := ct.NewTestCAPool(ca)
|
|
|
|
makeCreds := func(name string, networks []netip.Prefix) handshake.GetCredentialFunc {
|
|
c, _, rawKey, _ := ct.NewTestCert(
|
|
cert.Version2, cert.Curve_CURVE25519, ca, caKey,
|
|
name, ca.NotBefore(), ca.NotAfter(), networks, nil, nil,
|
|
)
|
|
priv, _, _, err := cert.UnmarshalPrivateKeyFromPEM(rawKey)
|
|
require.NoError(t, err)
|
|
hsBytes, err := c.MarshalForHandshakes()
|
|
require.NoError(t, err)
|
|
ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
|
|
cred := handshake.NewCredential(c, hsBytes, priv, ncs)
|
|
return func(v cert.Version) *handshake.Credential {
|
|
if v == cert.Version2 {
|
|
return cred
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
verifier := func(c cert.Certificate) (*cert.CachedCertificate, error) {
|
|
return caPool.VerifyCertificate(time.Now(), c)
|
|
}
|
|
|
|
initCreds := makeCreds("initiator", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
|
|
respCreds := makeCreds("responder", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
|
|
|
|
initM, err := handshake.NewMachine(
|
|
cert.Version2, initCreds, verifier,
|
|
func() (uint32, error) { return 1000, nil },
|
|
true, header.HandshakeIXPSK0,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
respM, err := handshake.NewMachine(
|
|
cert.Version2, respCreds, verifier,
|
|
func() (uint32, error) { return 2000, nil },
|
|
false, header.HandshakeIXPSK0,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
msg1, err := initM.Initiate(nil)
|
|
require.NoError(t, err)
|
|
|
|
resp, respR, err := respM.ProcessPacket(nil, msg1)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, respR)
|
|
|
|
_, initR, err = initM.ProcessPacket(nil, resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, initR)
|
|
|
|
return initR, respR
|
|
}
|
|
|
|
func TestConnectionState_NextMessageCounter(t *testing.T) {
|
|
cs := &ConnectionState{}
|
|
cs.messageCounter.Store(RejectAfterMessages - 2)
|
|
|
|
c, ok := cs.NextMessageCounter()
|
|
assert.True(t, ok)
|
|
assert.Equal(t, RejectAfterMessages-1, c)
|
|
|
|
// Hitting the limit refuses and pins the counter there
|
|
c, ok = cs.NextMessageCounter()
|
|
assert.False(t, ok)
|
|
assert.Equal(t, RejectAfterMessages, c)
|
|
assert.Equal(t, RejectAfterMessages, cs.messageCounter.Load())
|
|
|
|
// Continued send attempts stay refused and the counter never wraps
|
|
for i := 0; i < 10; i++ {
|
|
_, ok = cs.NextMessageCounter()
|
|
assert.False(t, ok)
|
|
}
|
|
assert.Equal(t, RejectAfterMessages, cs.messageCounter.Load())
|
|
}
|
|
|
|
// TestSendNoMetricsDropsExhausted drives the send path to the exhausted drop; metric and out flag prove it.
|
|
func TestSendNoMetricsDropsExhausted(t *testing.T) {
|
|
initR, _ := runTestHandshake(t)
|
|
ci, err := newConnectionStateFromResult(initR)
|
|
require.NoError(t, err)
|
|
ci.messageCounter.Store(RejectAfterMessages - 1)
|
|
|
|
f := &Interface{l: test.NewLogger(), messageMetrics: &MessageMetrics{txExhausted: metrics.NewCounter()}}
|
|
hostinfo := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, ConnectionState: ci}
|
|
|
|
f.sendNoMetrics(header.Message, 0, ci, hostinfo, netip.AddrPort{}, []byte{}, make([]byte, 12), make([]byte, mtu), 0)
|
|
|
|
// The crossing send is refused: it records an exhaustion drop and never reaches connectionManager.Out.
|
|
assert.Equal(t, int64(1), f.messageMetrics.txExhausted.Count())
|
|
assert.False(t, hostinfo.out.Load())
|
|
}
|
|
|
|
func TestNewConnectionStateFromResult(t *testing.T) {
|
|
initR, respR := runTestHandshake(t)
|
|
|
|
t.Run("initiator", func(t *testing.T) {
|
|
ci, err := newConnectionStateFromResult(initR)
|
|
require.NoError(t, err)
|
|
assert.True(t, ci.initiator)
|
|
assert.Equal(t, initR.MyCert, ci.myCert)
|
|
assert.Equal(t, initR.RemoteCert, ci.peerCert)
|
|
assert.NotNil(t, ci.eKey)
|
|
assert.NotNil(t, ci.dKey)
|
|
|
|
// IX has 2 handshake messages; the next data-plane send is counter=3.
|
|
assert.Equal(t, uint64(2), ci.messageCounter.Load(),
|
|
"messageCounter must equal Result.MessageIndex so the next send is N+1")
|
|
|
|
// Both handshake counters must be marked seen so they don't appear lost.
|
|
// Check returns false if an index has already been recorded.
|
|
assert.False(t, ci.window.Check(nil, 1), "counter 1 must already be seen")
|
|
assert.False(t, ci.window.Check(nil, 2), "counter 2 must already be seen")
|
|
// Counter 3 is the next data-plane message and must NOT be pre-marked.
|
|
assert.True(t, ci.window.Check(nil, 3), "counter 3 must not be pre-seeded")
|
|
})
|
|
|
|
t.Run("message index too large is refused", func(t *testing.T) {
|
|
bad := *initR
|
|
bad.MessageIndex = ReplayWindow
|
|
ci, err := newConnectionStateFromResult(&bad)
|
|
require.Error(t, err)
|
|
assert.Nil(t, ci)
|
|
})
|
|
|
|
t.Run("responder", func(t *testing.T) {
|
|
ci, err := newConnectionStateFromResult(respR)
|
|
require.NoError(t, err)
|
|
assert.False(t, ci.initiator)
|
|
assert.Equal(t, respR.MyCert, ci.myCert)
|
|
assert.Equal(t, respR.RemoteCert, ci.peerCert)
|
|
assert.NotNil(t, ci.eKey)
|
|
assert.NotNil(t, ci.dKey)
|
|
assert.Equal(t, uint64(2), ci.messageCounter.Load())
|
|
})
|
|
}
|