recovery_timing_test.go 4.2 KB

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