| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- //go:build e2e_testing
- // +build e2e_testing
- package e2e
- import (
- "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/udp"
- )
- // TestRecoveryTiming measures how long a tunnel takes to come back after the peer stops accepting our traffic,
- // which is what a laptop waking on a new network looks like from the peer's side: its NAT has no state for where
- // we are now, so everything we send disappears.
- //
- // It is a measurement, not a pass/fail assertion. Recovery is timed to the moment the peer punches back at us,
- // since that is when its NAT opens and the tunnel is usable again.
- //
- // go test -tags e2e_testing -v -run TestRecoveryTiming ./e2e/
- func TestRecoveryTiming(t *testing.T) {
- for _, tc := range []struct {
- name string
- rebind bool
- }{
- {"no trigger", false},
- {"rebind counter", true},
- } {
- t.Run(tc.name, func(t *testing.T) {
- d, lost := measureRecovery(t, tc.rebind)
- t.Logf("RESULT %-16s recovered in %-9v (%d packets lost)", tc.name, d.Round(time.Millisecond), lost)
- })
- }
- }
- // measureRecovery returns how long until the peer punched back, and how many of our packets died meanwhile. When
- // rebind is true we call RebindUDPServer once the tunnel goes dark, which is what the darwin network change
- // monitor does and what iOS has always done. When false, nothing tells nebula anything is wrong.
- func measureRecovery(t *testing.T, rebind bool) (time.Duration, int) {
- t.Helper()
- ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
- lhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "lh", "10.128.0.1/24", m{
- "lighthouse": m{"am_lighthouse": true},
- })
- peerCfg := m{
- "lighthouse": m{
- "hosts": []any{lhVpnIpNet[0].Addr().String()},
- "interval": 600,
- "local_allow_list": m{
- "10.0.0.0/24": true,
- "::/0": false,
- },
- },
- "static_host_map": m{
- lhVpnIpNet[0].Addr().String(): []any{lhUdpAddr.String()},
- },
- }
- myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.2/24", peerCfg)
- theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "them", "10.128.0.3/24", peerCfg)
- r := router.NewR(t, lhControl, myControl, theirControl)
- defer r.RenderFlow()
- defer func() {
- lhControl.Stop()
- myControl.Stop()
- theirControl.Stop()
- }()
- lhControl.Start()
- myControl.Start()
- theirControl.Start()
- r.RouteFor(time.Millisecond * 500)
- myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
- theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
- myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("establish")))
- r.RouteFor(time.Second)
- if myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false) == nil {
- t.Fatal("failed to establish the tunnel we are measuring")
- }
- r.RouteFor(time.Millisecond * 500)
- // From here the peer's NAT has no state for us, everything we send it disappears
- start := time.Now()
- blackholed := 0
- var recovered time.Duration
- if rebind {
- myControl.RebindUDPServer()
- }
- // Keep the tun busy the way someone retrying a stalled connection would
- stop := make(chan struct{})
- defer close(stop)
- go func() {
- tick := time.NewTicker(time.Millisecond * 200)
- defer tick.Stop()
- for {
- select {
- case <-stop:
- return
- case <-tick.C:
- myControl.InjectTunPacket(BuildTunUDPPacket(
- theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("retry")))
- }
- }
- }()
- r.RouteForAllExitFuncOrTimeout(time.Second*30, func(p *udp.Packet, c *nebula.Control) router.ExitType {
- if c == theirControl && p.From == myControl.GetUDPAddr() {
- blackholed++
- return router.Drop
- }
- // The peer reaching us directly is the moment its NAT opened, whether that is a punch or a handshake
- if c == myControl && p.From == theirUdpAddr {
- recovered = time.Since(start)
- return router.RouteAndExit
- }
- return router.KeepRouting
- })
- if recovered == 0 {
- t.Fatalf("no recovery within 30s (%d packets blackholed)", blackholed)
- }
- return recovered, blackholed
- }
|