mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 18:56:52 +01:00
477 lines
18 KiB
Go
477 lines
18 KiB
Go
//go:build e2e_testing
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"net/netip"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/slackhq/nebula"
|
|
"github.com/slackhq/nebula/cert"
|
|
"github.com/slackhq/nebula/cert_test"
|
|
"github.com/slackhq/nebula/e2e/router"
|
|
"github.com/slackhq/nebula/handshake/fragment"
|
|
"github.com/slackhq/nebula/header"
|
|
"github.com/slackhq/nebula/udp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func pqRequire() m {
|
|
return m{"handshakes": m{"pq": m{"require": true, "repair_delay": "0s"}}}
|
|
}
|
|
|
|
func pqConfig(initiate, accept, sticky bool) m {
|
|
return m{"handshakes": m{"pq": m{
|
|
"initiate": initiate,
|
|
"accept": accept,
|
|
"sticky": sticky,
|
|
"repair_delay": "0s",
|
|
}}}
|
|
}
|
|
|
|
// handshakeOf returns the handshake header p carries, looking inside relay
|
|
// wrapping, plus the fragment header when the handshake is fragmented.
|
|
func handshakeOf(p *udp.Packet) (*header.H, *fragment.Header) {
|
|
b := p.Data
|
|
h := &header.H{}
|
|
if h.Parse(b) != nil {
|
|
return nil, nil
|
|
}
|
|
if h.Type == header.Message && h.Subtype == header.MessageRelay {
|
|
// Relays authenticate but do not encrypt the inner packet.
|
|
b = b[header.Len:]
|
|
if h.Parse(b) != nil {
|
|
return nil, nil
|
|
}
|
|
}
|
|
if h.Type != header.Handshake {
|
|
return nil, nil
|
|
}
|
|
if !header.IsFragmented(h.Subtype) {
|
|
return h, nil
|
|
}
|
|
// A relayed packet still has the relay's AEAD tag on the end.
|
|
for _, tag := range []int{0, 16} {
|
|
if len(b) < header.Len+tag {
|
|
break
|
|
}
|
|
if fh, _, err := fragment.Parse(b[header.Len : len(b)-tag]); err == nil {
|
|
return h, &fh
|
|
}
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
// handshakeTally counts handshake packets by style as the router moves them.
|
|
type handshakeTally struct {
|
|
sync.Mutex
|
|
pq, classical int
|
|
maxPQLen int
|
|
}
|
|
|
|
func (ht *handshakeTally) observe(p *udp.Packet) {
|
|
h, _ := handshakeOf(p)
|
|
if h == nil {
|
|
return
|
|
}
|
|
ht.Lock()
|
|
defer ht.Unlock()
|
|
if h.Subtype == header.HandshakeIXPSK2MLKEM1024 {
|
|
ht.pq++
|
|
ht.maxPQLen = max(ht.maxPQLen, len(p.Data))
|
|
} else {
|
|
ht.classical++
|
|
}
|
|
}
|
|
|
|
// isTunnelData reports whether p is ordinary tunnel traffic headed for to,
|
|
// directly or as the last hop of a relay.
|
|
func isTunnelData(p *udp.Packet, receiver, to *nebula.Control) bool {
|
|
h := &header.H{}
|
|
if receiver != to || h.Parse(p.Data) != nil || h.Type != header.Message {
|
|
return false
|
|
}
|
|
if h.Subtype == header.MessageRelay {
|
|
if h.Parse(p.Data[header.Len:]) != nil {
|
|
return false
|
|
}
|
|
}
|
|
return h.Type == header.Message && h.Subtype == header.MessageNone
|
|
}
|
|
|
|
func newPQPair(t *testing.T, mine, theirs m) (myControl, theirControl *nebula.Control, myVpn, theirVpn []netip.Prefix, myUdp, theirUdp netip.AddrPort, r *router.R) {
|
|
t.Helper()
|
|
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
|
myControl, myVpn, myUdp, _ = newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.1/24", mine)
|
|
theirControl, theirVpn, theirUdp, _ = newSimpleServer(cert.Version2, ca, caKey, "them", "10.128.0.2/24", theirs)
|
|
myControl.InjectLightHouseAddr(theirVpn[0].Addr(), theirUdp)
|
|
r = router.NewR(t, myControl, theirControl)
|
|
myControl.Start()
|
|
theirControl.Start()
|
|
return
|
|
}
|
|
|
|
func TestPQHandshake(t *testing.T) {
|
|
t.Parallel()
|
|
// They only accept, which is all a PQ initiator needs.
|
|
myControl, theirControl, myVpn, theirVpn, myUdp, theirUdp, r := newPQPair(t, pqConfig(true, true, false), pqConfig(false, true, false))
|
|
defer r.RenderFlow()
|
|
|
|
var tally handshakeTally
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(5*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
|
|
assert.Zero(t, tally.classical, "no classical handshake should be needed")
|
|
assert.GreaterOrEqual(t, tally.pq, 4, "both messages travel as several fragments")
|
|
assert.LessOrEqual(t, tally.maxPQLen, fragment.MaxPacketLen)
|
|
|
|
assertHostInfoPair(t, myUdp, theirUdp, myVpn, theirVpn, myControl, theirControl)
|
|
assertTunnel(t, myVpn[0].Addr(), theirVpn[0].Addr(), myControl, theirControl, r)
|
|
assert.Empty(t, myControl.ListHostmapIndexes(true), "no pending index may outlive the handshake")
|
|
assert.Empty(t, theirControl.ListHostmapIndexes(true))
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQHandshakeLoss drops data fragments of both messages; the repair
|
|
// fragments must carry the handshake without a retry.
|
|
func TestPQHandshakeLoss(t *testing.T) {
|
|
t.Parallel()
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, pqConfig(true, true, false), pqConfig(false, true, false))
|
|
defer r.RenderFlow()
|
|
|
|
var tally handshakeTally
|
|
dropped := 0
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(5*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
if h, fh := handshakeOf(p); fh != nil {
|
|
// Every data fragment of msg1, and the first of msg2.
|
|
if (h.MessageCounter == 1 && int(fh.Index) < 2) || (h.MessageCounter == 2 && fh.Index == 0) {
|
|
dropped++
|
|
return router.Drop
|
|
}
|
|
}
|
|
tally.observe(p)
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
|
|
assert.Equal(t, 3, dropped)
|
|
assert.Zero(t, tally.classical, "the first attempt should succeed from repair fragments")
|
|
assertTunnel(t, myVpn[0].Addr(), theirVpn[0].Addr(), myControl, theirControl, r)
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQFallback has a peer that does not accept PQ handshakes. The initiator's
|
|
// next attempt is classical and the tunnel comes up anyway.
|
|
func TestPQFallback(t *testing.T) {
|
|
t.Parallel()
|
|
myControl, theirControl, myVpn, theirVpn, myUdp, theirUdp, r := newPQPair(t, pqConfig(true, true, false), nil)
|
|
defer r.RenderFlow()
|
|
|
|
var tally handshakeTally
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(5*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
|
|
assert.Positive(t, tally.pq, "a PQ attempt comes first")
|
|
assert.Positive(t, tally.classical, "then a classical one")
|
|
|
|
assertHostInfoPair(t, myUdp, theirUdp, myVpn, theirVpn, myControl, theirControl)
|
|
assertTunnel(t, myVpn[0].Addr(), theirVpn[0].Addr(), myControl, theirControl, r)
|
|
assert.Empty(t, myControl.ListHostmapIndexes(true), "the losing PQ attempt's index must be released")
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQResendFreshSymbols loses the responder's whole first msg2. The
|
|
// initiator's next PQ attempt offers fresh msg1 symbols, the responder
|
|
// recognises the repeat and resends msg2 with symbols it has not sent before.
|
|
func TestPQResendFreshSymbols(t *testing.T) {
|
|
t.Parallel()
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, pqConfig(true, true, false), pqConfig(false, true, false))
|
|
defer r.RenderFlow()
|
|
|
|
var firstMsg2, freshMsg2, freshMsg1 int
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(10*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
h, fh := handshakeOf(p)
|
|
switch {
|
|
case h != nil && fh == nil:
|
|
// Keep the classical fallback out of the way.
|
|
return router.Drop
|
|
case fh != nil && h.MessageCounter == 2 && fh.Index < 4:
|
|
firstMsg2++
|
|
return router.Drop
|
|
case fh != nil && h.MessageCounter == 2:
|
|
freshMsg2++
|
|
case fh != nil && h.MessageCounter == 1 && fh.Index >= 4:
|
|
freshMsg1++
|
|
}
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
|
|
assert.Equal(t, 4, firstMsg2, "the first msg2 is 2 data and 2 repair fragments")
|
|
assert.Positive(t, freshMsg1, "the retry offers fresh msg1 symbols")
|
|
assert.Positive(t, freshMsg2, "the resend offers fresh msg2 symbols")
|
|
assertTunnel(t, myVpn[0].Addr(), theirVpn[0].Addr(), myControl, theirControl, r)
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQSticky checks a sticky initiator never offers a classical handshake to a
|
|
// peer it has already completed a PQ handshake with.
|
|
func TestPQSticky(t *testing.T) {
|
|
t.Parallel()
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, pqConfig(true, true, true), pqConfig(false, true, false))
|
|
defer r.RenderFlow()
|
|
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(5*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
theirControl.GetFromTun(true)
|
|
|
|
t.Log("Rehandshake while an attacker drops every PQ packet")
|
|
var tally handshakeTally
|
|
myControl.ReHandshake(theirVpn[0].Addr())
|
|
r.RouteForAllExitFuncOrTimeout(time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
if h, _ := handshakeOf(p); h != nil && h.Subtype == header.HandshakeIXPSK2MLKEM1024 {
|
|
return router.Drop
|
|
}
|
|
return router.KeepRouting
|
|
})
|
|
assert.Positive(t, tally.pq, "PQ attempts keep being made")
|
|
assert.Zero(t, tally.classical, "sticky must never fall back")
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
func TestPQRelays(t *testing.T) {
|
|
t.Parallel()
|
|
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
|
useRelays := pqConfig(true, true, false)
|
|
useRelays["relay"] = m{"use_relays": true}
|
|
amRelay := pqConfig(true, true, false)
|
|
amRelay["relay"] = m{"am_relay": true}
|
|
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, "me ", "10.128.0.1/24", useRelays)
|
|
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "relay ", "10.128.0.128/24", amRelay)
|
|
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them ", "10.128.0.2/24", useRelays)
|
|
|
|
myControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)
|
|
myControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})
|
|
relayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
|
|
|
r := router.NewR(t, myControl, relayControl, theirControl)
|
|
defer r.RenderFlow()
|
|
|
|
myControl.Start()
|
|
relayControl.Start()
|
|
theirControl.Start()
|
|
|
|
var tally handshakeTally
|
|
var relayedPQ, relayedClassical int
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(10*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
outer := &header.H{}
|
|
if outer.Parse(p.Data) == nil && outer.Type == header.Message && outer.Subtype == header.MessageRelay {
|
|
if h, fh := handshakeOf(p); h != nil && fh != nil {
|
|
relayedPQ++
|
|
assert.LessOrEqual(t, len(p.Data), fragment.MaxPacketLen+32, "a relayed fragment fits the IPv6 minimum MTU")
|
|
} else if h != nil {
|
|
relayedClassical++
|
|
}
|
|
}
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)
|
|
assert.Positive(t, relayedPQ, "the end to end handshake is PQ through the relay")
|
|
assert.Zero(t, relayedClassical, "the relay coming up carries the PQ attempt, not the classical retry")
|
|
r.RenderHostmaps("Final hostmaps", myControl, relayControl, theirControl)
|
|
|
|
myControl.Stop()
|
|
relayControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQTimeoutReleasesIndexes lets a handshake with both a PQ and a classical
|
|
// attempt pending time out, and checks neither index is left behind.
|
|
func TestPQTimeoutReleasesIndexes(t *testing.T) {
|
|
t.Parallel()
|
|
mine := pqConfig(true, true, false)
|
|
mine["handshakes"].(m)["retries"] = 2
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, mine, pqConfig(false, true, false))
|
|
defer r.RenderFlow()
|
|
|
|
var tally handshakeTally
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
r.RouteForAllExitFuncOrTimeout(1500*time.Millisecond, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
return router.Drop
|
|
})
|
|
|
|
assert.Positive(t, tally.pq)
|
|
assert.Positive(t, tally.classical)
|
|
assert.Nil(t, myControl.GetHostInfoByVpnAddr(theirVpn[0].Addr(), true), "the handshake should have timed out")
|
|
assert.Empty(t, myControl.ListHostmapIndexes(true))
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQCookieUnderLoad floods the responder with msg1 fragments from spoofed
|
|
// sources until it demands cookies, then checks a real initiator picks up the
|
|
// cookie and still completes a PQ handshake.
|
|
func TestPQCookieUnderLoad(t *testing.T) {
|
|
t.Parallel()
|
|
myControl, theirControl, myVpn, theirVpn, _, theirUdp, r := newPQPair(t, pqConfig(true, true, false), pqConfig(false, true, false))
|
|
defer r.RenderFlow()
|
|
|
|
// Half of the 1024 entry table is the cookie threshold. Stopping exactly there
|
|
// keeps them from answering the spoofed sources, which the router cannot reach.
|
|
t.Log("Flood them with the first fragment of msg1s nobody will finish")
|
|
for i := range 512 {
|
|
from := netip.AddrPortFrom(netip.AddrFrom4([4]byte{198, 18, byte(i >> 8), byte(i)}), 4242)
|
|
s, err := fragment.NewSender(make([]byte, 2000), header.HandshakeIXPSK2MLKEM1024, 0, 1, uint32(i+1))
|
|
require.NoError(t, err)
|
|
pkts, _, err := s.Attempt(0)
|
|
require.NoError(t, err)
|
|
theirControl.InjectUDPPacket(&udp.Packet{From: from, To: theirUdp, Data: pkts[0]})
|
|
}
|
|
|
|
var tally handshakeTally
|
|
var cookieReplies, msg2, withCookie int
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(5*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
if h, fh := handshakeOf(p); h != nil && h.Subtype == header.HandshakeIXPSK2MLKEM1024 {
|
|
switch {
|
|
case h.MessageCounter == 0:
|
|
cookieReplies++
|
|
case h.MessageCounter == 2:
|
|
msg2++
|
|
case fh != nil && fh.HasCookie():
|
|
withCookie++
|
|
}
|
|
}
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
|
|
assert.Positive(t, cookieReplies, "a loaded responder asks for a cookie")
|
|
assert.Positive(t, withCookie, "the initiator resends with it")
|
|
assert.Positive(t, msg2, "and the handshake completes post-quantum")
|
|
assert.Zero(t, tally.classical, "the cookie round trip beats the classical fallback")
|
|
assertTunnel(t, myVpn[0].Addr(), theirVpn[0].Addr(), myControl, theirControl, r)
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
}
|
|
|
|
// TestPQRequire checks require mode never handshakes classically, in either
|
|
// direction.
|
|
func TestPQRequire(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("initiator", func(t *testing.T) {
|
|
// They neither speak PQ nor require it; we never fall back.
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, pqRequire(), nil)
|
|
defer r.RenderFlow()
|
|
|
|
var tally handshakeTally
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
r.RouteForAllExitFuncOrTimeout(time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
return router.KeepRouting
|
|
})
|
|
assert.Positive(t, tally.pq)
|
|
assert.Zero(t, tally.classical)
|
|
assert.Nil(t, theirControl.GetHostInfoByVpnAddr(myVpn[0].Addr(), false), "no tunnel without PQ")
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
})
|
|
|
|
t.Run("responder", func(t *testing.T) {
|
|
// We only speak classical; they refuse it.
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, nil, pqRequire())
|
|
defer r.RenderFlow()
|
|
|
|
var answers int
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
r.RouteForAllExitFuncOrTimeout(time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
if h, _ := handshakeOf(p); h != nil && h.MessageCounter == 2 {
|
|
answers++
|
|
}
|
|
return router.KeepRouting
|
|
})
|
|
assert.Zero(t, answers, "a classical msg1 gets no answer")
|
|
assert.Nil(t, theirControl.GetHostInfoByVpnAddr(myVpn[0].Addr(), false))
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
})
|
|
|
|
t.Run("both", func(t *testing.T) {
|
|
myControl, theirControl, myVpn, theirVpn, _, _, r := newPQPair(t, pqRequire(), pqRequire())
|
|
defer r.RenderFlow()
|
|
|
|
var tally handshakeTally
|
|
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
|
|
require.True(t, r.RouteForAllExitFuncOrTimeout(5*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
|
|
tally.observe(p)
|
|
if isTunnelData(p, receiver, theirControl) {
|
|
return router.RouteAndExit
|
|
}
|
|
return router.KeepRouting
|
|
}), "tunnel never came up")
|
|
assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
|
|
assert.Zero(t, tally.classical)
|
|
assertTunnel(t, myVpn[0].Addr(), theirVpn[0].Addr(), myControl, theirControl, r)
|
|
|
|
myControl.Stop()
|
|
theirControl.Stop()
|
|
})
|
|
}
|