| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- package nebula
- import (
- "bytes"
- "log/slog"
- "net/netip"
- "testing"
- "github.com/gaissmai/bart"
- "github.com/slackhq/nebula/cert"
- "github.com/slackhq/nebula/test"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TestStartRelaysLogDedupe verifies that repeated attempts with the same relay set drop the log
- // chatter to Debug, mirroring how the normal handshake retry loop quiets down once it's already
- // announced its targets.
- func TestStartRelaysLogDedupe(t *testing.T) {
- vpnIp := netip.MustParseAddr("100.64.99.4")
- otherRelay := netip.MustParseAddr("100.64.99.5")
- newHH := func() *HandshakeHostInfo {
- // Use the target's own vpnIp as the "relay" so the loop body skips it without
- // touching any sender-side state. That isolates the test to the level-selection
- // behavior of the top-level "Attempt to relay through hosts" log.
- hostinfo := &HostInfo{
- vpnAddrs: []netip.Addr{vpnIp},
- localIndexId: 1,
- remotes: NewRemoteList([]netip.Addr{vpnIp}, nil),
- }
- hostinfo.remotes.relays = []netip.Addr{vpnIp}
- return &HandshakeHostInfo{hostinfo: hostinfo}
- }
- // Park any extra relay addresses we'll introduce mid-test in myVpnAddrsTable so the loop
- // body always skips before touching f.Handshake (which would need a real handshakeManager).
- addrTable := new(bart.Lite)
- addrTable.Insert(netip.PrefixFrom(otherRelay, otherRelay.BitLen()))
- f := &Interface{myVpnAddrsTable: addrTable}
- newRM := func(buf *bytes.Buffer) *relayManager {
- l := test.NewLoggerWithOutputAndLevel(buf, slog.LevelDebug)
- rm := &relayManager{l: l, hostmap: newHostMap(l)}
- rm.useRelays.Store(true)
- return rm
- }
- const msg = `msg="Attempt to relay through hosts"`
- t.Run("first attempt logs at Info", func(t *testing.T) {
- var buf bytes.Buffer
- rm := newRM(&buf)
- hh := newHH()
- rm.StartRelays(f, vpnIp, hh, nil)
- assert.Equal(t, []netip.Addr{vpnIp}, hh.lastRelays, "lastRelays should record the relay set we just attempted")
- assert.Contains(t, buf.String(), "level=INFO "+msg, "expected Info level on first attempt")
- })
- t.Run("repeat attempt with same relays drops to Debug", func(t *testing.T) {
- var buf bytes.Buffer
- rm := newRM(&buf)
- hh := newHH()
- rm.StartRelays(f, vpnIp, hh, nil)
- first := append([]netip.Addr(nil), hh.lastRelays...)
- buf.Reset()
- rm.StartRelays(f, vpnIp, hh, nil)
- assert.Equal(t, first, hh.lastRelays)
- assert.Contains(t, buf.String(), "level=DEBUG "+msg, "expected Debug level on identical retry")
- assert.NotContains(t, buf.String(), "level=INFO "+msg, "Info should not fire on identical retry")
- })
- t.Run("changed relay list bumps back to Info", func(t *testing.T) {
- var buf bytes.Buffer
- rm := newRM(&buf)
- hh := newHH()
- rm.StartRelays(f, vpnIp, hh, nil)
- buf.Reset()
- // The lighthouse handed us a new set this round.
- hh.hostinfo.remotes.relays = []netip.Addr{vpnIp, otherRelay}
- rm.StartRelays(f, vpnIp, hh, nil)
- assert.Equal(t, []netip.Addr{vpnIp, otherRelay}, hh.lastRelays)
- assert.Contains(t, buf.String(), "level=INFO "+msg, "expected Info when the relay list changes")
- })
- t.Run("disabled relays clears lastRelays and emits no Attempt log", func(t *testing.T) {
- var buf bytes.Buffer
- rm := newRM(&buf)
- rm.useRelays.Store(false)
- hh := newHH()
- hh.lastRelays = []netip.Addr{vpnIp}
- rm.StartRelays(f, vpnIp, hh, nil)
- assert.Nil(t, hh.lastRelays, "with relays disabled lastRelays should be cleared")
- assert.NotContains(t, buf.String(), msg, "should not log when we shortcut out")
- })
- }
- // TestHandleCreateRelayRequestRejectsSpoofedFrom verifies that a relay only forwards a CreateRelayRequest
- // when relayFrom is the peer that sent it. Otherwise any peer could reset another host's established
- // relay back to Requested, or allocate unbounded relay entries on the target's hostinfo.
- func TestHandleCreateRelayRequestRejectsSpoofedFrom(t *testing.T) {
- l := test.NewLogger()
- relayAddr := netip.MustParseAddr("10.128.0.128")
- malloryAddr := netip.MustParseAddr("10.128.0.3")
- victimAddr := netip.MustParseAddr("10.128.0.1")
- targetAddr := netip.MustParseAddr("10.128.0.2")
- addrTable := new(bart.Lite)
- addrTable.Insert(netip.PrefixFrom(relayAddr, relayAddr.BitLen()))
- hm := newHostMap(l)
- f := &Interface{myVpnAddrsTable: addrTable, hostMap: hm}
- rm := &relayManager{l: l, hostmap: hm}
- rm.amRelay.Store(true)
- newRelayState := func() RelayState {
- return RelayState{relayForByAddr: map[netip.Addr]*Relay{}, relayForByIdx: map[uint32]*Relay{}}
- }
- mallory := &HostInfo{vpnAddrs: []netip.Addr{malloryAddr}, localIndexId: 1, relayState: newRelayState()}
- target := &HostInfo{
- vpnAddrs: []netip.Addr{targetAddr},
- localIndexId: 2,
- remotes: NewRemoteList([]netip.Addr{targetAddr}, nil),
- relayState: newRelayState(),
- }
- target.SetRemote(netip.MustParseAddrPort("192.0.2.2:4242"))
- hm.unlockedAddHostInfo(mallory, f)
- hm.unlockedAddHostInfo(target, f)
- // The victim already has a working relay to the target through us.
- _, err := AddRelay(l, target, hm, victimAddr, nil, ForwardingType, Established)
- require.NoError(t, err)
- relaysBefore := len(hm.Relays)
- for i := range 10 {
- spoofed := netip.AddrFrom4([4]byte{10, 128, 1, byte(i)})
- for _, from := range []netip.Addr{victimAddr, spoofed} {
- rm.handleCreateRelayRequest(cert.Version2, mallory, f, &NebulaControl{
- Type: NebulaControl_CreateRelayRequest,
- InitiatorRelayIndex: uint32(100 + i),
- RelayFromAddr: netAddrToProtoAddr(from),
- RelayToAddr: netAddrToProtoAddr(targetAddr),
- })
- }
- }
- r, ok := target.relayState.QueryRelayForByIp(victimAddr)
- require.True(t, ok)
- assert.Equal(t, Established, r.State, "victim's relay must not be reset by a request from another peer")
- assert.Len(t, hm.Relays, relaysBefore, "spoofed requests must not allocate relay entries")
- assert.Empty(t, mallory.relayState.CopyAllRelayFor(), "spoofed requests must not create state on the sender")
- }
|