relay_manager_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package nebula
  2. import (
  3. "bytes"
  4. "log/slog"
  5. "net/netip"
  6. "testing"
  7. "github.com/gaissmai/bart"
  8. "github.com/slackhq/nebula/cert"
  9. "github.com/slackhq/nebula/test"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. )
  13. // TestStartRelaysLogDedupe verifies that repeated attempts with the same relay set drop the log
  14. // chatter to Debug, mirroring how the normal handshake retry loop quiets down once it's already
  15. // announced its targets.
  16. func TestStartRelaysLogDedupe(t *testing.T) {
  17. vpnIp := netip.MustParseAddr("100.64.99.4")
  18. otherRelay := netip.MustParseAddr("100.64.99.5")
  19. newHH := func() *HandshakeHostInfo {
  20. // Use the target's own vpnIp as the "relay" so the loop body skips it without
  21. // touching any sender-side state. That isolates the test to the level-selection
  22. // behavior of the top-level "Attempt to relay through hosts" log.
  23. hostinfo := &HostInfo{
  24. vpnAddrs: []netip.Addr{vpnIp},
  25. localIndexId: 1,
  26. remotes: NewRemoteList([]netip.Addr{vpnIp}, nil),
  27. }
  28. hostinfo.remotes.relays = []netip.Addr{vpnIp}
  29. return &HandshakeHostInfo{hostinfo: hostinfo}
  30. }
  31. // Park any extra relay addresses we'll introduce mid-test in myVpnAddrsTable so the loop
  32. // body always skips before touching f.Handshake (which would need a real handshakeManager).
  33. addrTable := new(bart.Lite)
  34. addrTable.Insert(netip.PrefixFrom(otherRelay, otherRelay.BitLen()))
  35. f := &Interface{myVpnAddrsTable: addrTable}
  36. newRM := func(buf *bytes.Buffer) *relayManager {
  37. l := test.NewLoggerWithOutputAndLevel(buf, slog.LevelDebug)
  38. rm := &relayManager{l: l, hostmap: newHostMap(l)}
  39. rm.useRelays.Store(true)
  40. return rm
  41. }
  42. const msg = `msg="Attempt to relay through hosts"`
  43. t.Run("first attempt logs at Info", func(t *testing.T) {
  44. var buf bytes.Buffer
  45. rm := newRM(&buf)
  46. hh := newHH()
  47. rm.StartRelays(f, vpnIp, hh, nil)
  48. assert.Equal(t, []netip.Addr{vpnIp}, hh.lastRelays, "lastRelays should record the relay set we just attempted")
  49. assert.Contains(t, buf.String(), "level=INFO "+msg, "expected Info level on first attempt")
  50. })
  51. t.Run("repeat attempt with same relays drops to Debug", func(t *testing.T) {
  52. var buf bytes.Buffer
  53. rm := newRM(&buf)
  54. hh := newHH()
  55. rm.StartRelays(f, vpnIp, hh, nil)
  56. first := append([]netip.Addr(nil), hh.lastRelays...)
  57. buf.Reset()
  58. rm.StartRelays(f, vpnIp, hh, nil)
  59. assert.Equal(t, first, hh.lastRelays)
  60. assert.Contains(t, buf.String(), "level=DEBUG "+msg, "expected Debug level on identical retry")
  61. assert.NotContains(t, buf.String(), "level=INFO "+msg, "Info should not fire on identical retry")
  62. })
  63. t.Run("changed relay list bumps back to Info", func(t *testing.T) {
  64. var buf bytes.Buffer
  65. rm := newRM(&buf)
  66. hh := newHH()
  67. rm.StartRelays(f, vpnIp, hh, nil)
  68. buf.Reset()
  69. // The lighthouse handed us a new set this round.
  70. hh.hostinfo.remotes.relays = []netip.Addr{vpnIp, otherRelay}
  71. rm.StartRelays(f, vpnIp, hh, nil)
  72. assert.Equal(t, []netip.Addr{vpnIp, otherRelay}, hh.lastRelays)
  73. assert.Contains(t, buf.String(), "level=INFO "+msg, "expected Info when the relay list changes")
  74. })
  75. t.Run("disabled relays clears lastRelays and emits no Attempt log", func(t *testing.T) {
  76. var buf bytes.Buffer
  77. rm := newRM(&buf)
  78. rm.useRelays.Store(false)
  79. hh := newHH()
  80. hh.lastRelays = []netip.Addr{vpnIp}
  81. rm.StartRelays(f, vpnIp, hh, nil)
  82. assert.Nil(t, hh.lastRelays, "with relays disabled lastRelays should be cleared")
  83. assert.NotContains(t, buf.String(), msg, "should not log when we shortcut out")
  84. })
  85. }
  86. // TestHandleCreateRelayRequestRejectsSpoofedFrom verifies that a relay only forwards a CreateRelayRequest
  87. // when relayFrom is the peer that sent it. Otherwise any peer could reset another host's established
  88. // relay back to Requested, or allocate unbounded relay entries on the target's hostinfo.
  89. func TestHandleCreateRelayRequestRejectsSpoofedFrom(t *testing.T) {
  90. l := test.NewLogger()
  91. relayAddr := netip.MustParseAddr("10.128.0.128")
  92. malloryAddr := netip.MustParseAddr("10.128.0.3")
  93. victimAddr := netip.MustParseAddr("10.128.0.1")
  94. targetAddr := netip.MustParseAddr("10.128.0.2")
  95. addrTable := new(bart.Lite)
  96. addrTable.Insert(netip.PrefixFrom(relayAddr, relayAddr.BitLen()))
  97. hm := newHostMap(l)
  98. f := &Interface{myVpnAddrsTable: addrTable, hostMap: hm}
  99. rm := &relayManager{l: l, hostmap: hm}
  100. rm.amRelay.Store(true)
  101. newRelayState := func() RelayState {
  102. return RelayState{relayForByAddr: map[netip.Addr]*Relay{}, relayForByIdx: map[uint32]*Relay{}}
  103. }
  104. mallory := &HostInfo{vpnAddrs: []netip.Addr{malloryAddr}, localIndexId: 1, relayState: newRelayState()}
  105. target := &HostInfo{
  106. vpnAddrs: []netip.Addr{targetAddr},
  107. localIndexId: 2,
  108. remotes: NewRemoteList([]netip.Addr{targetAddr}, nil),
  109. relayState: newRelayState(),
  110. }
  111. target.SetRemote(netip.MustParseAddrPort("192.0.2.2:4242"))
  112. hm.unlockedAddHostInfo(mallory, f)
  113. hm.unlockedAddHostInfo(target, f)
  114. // The victim already has a working relay to the target through us.
  115. _, err := AddRelay(l, target, hm, victimAddr, nil, ForwardingType, Established)
  116. require.NoError(t, err)
  117. relaysBefore := len(hm.Relays)
  118. for i := range 10 {
  119. spoofed := netip.AddrFrom4([4]byte{10, 128, 1, byte(i)})
  120. for _, from := range []netip.Addr{victimAddr, spoofed} {
  121. rm.handleCreateRelayRequest(cert.Version2, mallory, f, &NebulaControl{
  122. Type: NebulaControl_CreateRelayRequest,
  123. InitiatorRelayIndex: uint32(100 + i),
  124. RelayFromAddr: netAddrToProtoAddr(from),
  125. RelayToAddr: netAddrToProtoAddr(targetAddr),
  126. })
  127. }
  128. }
  129. r, ok := target.relayState.QueryRelayForByIp(victimAddr)
  130. require.True(t, ok)
  131. assert.Equal(t, Established, r.State, "victim's relay must not be reset by a request from another peer")
  132. assert.Len(t, hm.Relays, relaysBefore, "spoofed requests must not allocate relay entries")
  133. assert.Empty(t, mallory.relayState.CopyAllRelayFor(), "spoofed requests must not create state on the sender")
  134. }