1
0

recovery_timing_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //go:build e2e_testing
  2. package e2e
  3. import (
  4. "testing"
  5. "time"
  6. "github.com/slackhq/nebula"
  7. "github.com/slackhq/nebula/cert"
  8. "github.com/slackhq/nebula/cert_test"
  9. "github.com/slackhq/nebula/e2e/router"
  10. "github.com/slackhq/nebula/udp"
  11. )
  12. // TestRecoveryTiming measures how long a tunnel takes to come back after the peer stops accepting our traffic,
  13. // which is what a laptop waking on a new network looks like from the peer's side: its NAT has no state for where
  14. // we are now, so everything we send disappears.
  15. //
  16. // It is a measurement, not a pass/fail assertion. Recovery is timed to the moment the peer punches back at us,
  17. // since that is when its NAT opens and the tunnel is usable again.
  18. //
  19. // go test -tags e2e_testing -v -run TestRecoveryTiming ./e2e/
  20. func TestRecoveryTiming(t *testing.T) {
  21. for _, tc := range []struct {
  22. name string
  23. rebind bool
  24. }{
  25. {"no trigger", false},
  26. {"rebind counter", true},
  27. } {
  28. t.Run(tc.name, func(t *testing.T) {
  29. d, lost := measureRecovery(t, tc.rebind)
  30. t.Logf("RESULT %-16s recovered in %-9v (%d packets lost)", tc.name, d.Round(time.Millisecond), lost)
  31. })
  32. }
  33. }
  34. // measureRecovery returns how long until the peer punched back, and how many of our packets died meanwhile. When
  35. // rebind is true we call RebindUDPServer once the tunnel goes dark, which is what the darwin network change
  36. // monitor does and what iOS has always done. When false, nothing tells nebula anything is wrong.
  37. func measureRecovery(t *testing.T, rebind bool) (time.Duration, int) {
  38. t.Helper()
  39. ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
  40. lhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "lh", "10.128.0.1/24", m{
  41. "lighthouse": m{"am_lighthouse": true},
  42. })
  43. peerCfg := m{
  44. "lighthouse": m{
  45. "hosts": []any{lhVpnIpNet[0].Addr().String()},
  46. "interval": 600,
  47. "local_allow_list": m{
  48. "10.0.0.0/24": true,
  49. "::/0": false,
  50. },
  51. },
  52. "static_host_map": m{
  53. lhVpnIpNet[0].Addr().String(): []any{lhUdpAddr.String()},
  54. },
  55. }
  56. myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.2/24", peerCfg)
  57. theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "them", "10.128.0.3/24", peerCfg)
  58. r := router.NewR(t, lhControl, myControl, theirControl)
  59. defer r.RenderFlow()
  60. defer func() {
  61. lhControl.Stop()
  62. myControl.Stop()
  63. theirControl.Stop()
  64. }()
  65. lhControl.Start()
  66. myControl.Start()
  67. theirControl.Start()
  68. r.RouteFor(time.Millisecond * 500)
  69. myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
  70. theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
  71. myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("establish")))
  72. r.RouteFor(time.Second)
  73. if myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false) == nil {
  74. t.Fatal("failed to establish the tunnel we are measuring")
  75. }
  76. r.RouteFor(time.Millisecond * 500)
  77. // From here the peer's NAT has no state for us, everything we send it disappears
  78. start := time.Now()
  79. blackholed := 0
  80. var recovered time.Duration
  81. if rebind {
  82. myControl.RebindUDPServer()
  83. }
  84. // Keep the tun busy the way someone retrying a stalled connection would
  85. stop := make(chan struct{})
  86. defer close(stop)
  87. go func() {
  88. tick := time.NewTicker(time.Millisecond * 200)
  89. defer tick.Stop()
  90. for {
  91. select {
  92. case <-stop:
  93. return
  94. case <-tick.C:
  95. myControl.InjectTunPacket(BuildTunUDPPacket(
  96. theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("retry")))
  97. }
  98. }
  99. }()
  100. r.RouteForAllExitFuncOrTimeout(time.Second*30, func(p *udp.Packet, c *nebula.Control) router.ExitType {
  101. if c == theirControl && p.From == myControl.GetUDPAddr() {
  102. blackholed++
  103. return router.Drop
  104. }
  105. // The peer reaching us directly is the moment its NAT opened, whether that is a punch or a handshake
  106. if c == myControl && p.From == theirUdpAddr {
  107. recovered = time.Since(start)
  108. return router.RouteAndExit
  109. }
  110. return router.KeepRouting
  111. })
  112. if recovered == 0 {
  113. t.Fatalf("no recovery within 30s (%d packets blackholed)", blackholed)
  114. }
  115. return recovered, blackholed
  116. }