From e50f8128f4373dd6d56568ec9b0b728e5a399a23 Mon Sep 17 00:00:00 2001 From: Matt Richardson Date: Fri, 4 Sep 2026 11:26:08 -0400 Subject: [PATCH] Drop hostQueries to our overlay addresses (#1866) * Drop hostQueries to our overlay addresses Don't bother responding to hostQuery's for our overlay addresses. --- lighthouse.go | 16 +++++- lighthouse_test.go | 134 +++++++++++++++++++++++++++------------------ 2 files changed, 95 insertions(+), 55 deletions(-) diff --git a/lighthouse.go b/lighthouse.go index 9cece233..df279069 100644 --- a/lighthouse.go +++ b/lighthouse.go @@ -34,7 +34,9 @@ type LightHouse struct { myVpnNetworks []netip.Prefix myVpnNetworksTable *bart.Lite - punchy *Punchy + // myVpnAddrsTable contains our overlay host addrs, as opposed to the overlay networks + myVpnAddrsTable *bart.Lite + punchy *Punchy // localAddrsFn enumerates the underlay addresses we advertise. It is a field so tests can supply simulated // addresses rather than whatever this machine's NICs happen to be. Set it before Start. @@ -104,6 +106,7 @@ func NewLightHouseFromConfig(ctx context.Context, l *slog.Logger, c *config.C, c amLighthouse: amLighthouse, myVpnNetworks: cs.myVpnNetworks, myVpnNetworksTable: cs.myVpnNetworksTable, + myVpnAddrsTable: cs.myVpnAddrsTable, addrMap: make(map[netip.Addr]*RemoteList), nebulaPort: nebulaPort, punchy: p, @@ -1158,6 +1161,17 @@ func (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, fromVpnAddrs []neti return } + // Don't respond to requests for us. + if lhh.lh.myVpnAddrsTable.Contains(queryVpnAddr) { + if lhh.l.Enabled(context.Background(), slog.LevelDebug) { + lhh.l.Debug("Ignoring HostQuery for one of my own addresses", + "fromVpnAddrs", fromVpnAddrs, + "queryVpnAddr", queryVpnAddr, + ) + } + return + } + found, ln, err := lhh.lh.queryAndPrepMessage(queryVpnAddr, func(c *cache) (int, error) { n = lhh.resetMeta() n.Type = NebulaMeta_HostQueryReply diff --git a/lighthouse_test.go b/lighthouse_test.go index 7a81e5d2..9f3ffddf 100644 --- a/lighthouse_test.go +++ b/lighthouse_test.go @@ -27,15 +27,27 @@ func TestOldIPv4Only(t *testing.T) { assert.Equal(t, binary.BigEndian.Uint32(bp[:]), m.GetAddr()) } +func testCertState(networks ...netip.Prefix) *CertState { + cs := &CertState{ + myVpnNetworks: networks, + myVpnNetworksTable: new(bart.Lite), + myVpnAddrs: make([]netip.Addr, 0, len(networks)), + myVpnAddrsTable: new(bart.Lite), + } + + for _, n := range networks { + cs.myVpnNetworksTable.Insert(n) + cs.myVpnAddrs = append(cs.myVpnAddrs, n.Addr()) + cs.myVpnAddrsTable.Insert(netip.PrefixFrom(n.Addr(), n.Addr().BitLen())) + } + + return cs +} + func Test_lhStaticMapping(t *testing.T) { l := test.NewLogger() myVpnNet := netip.MustParsePrefix("10.128.0.1/16") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh1 := "10.128.0.2" c := config.NewC(l) @@ -55,12 +67,7 @@ func Test_lhStaticMapping(t *testing.T) { func TestReloadLighthouseInterval(t *testing.T) { l := test.NewLogger() myVpnNet := netip.MustParsePrefix("10.128.0.1/16") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh1 := "10.128.0.2" c := config.NewC(l) @@ -90,12 +97,7 @@ func TestReloadLighthouseInterval(t *testing.T) { func BenchmarkLighthouseHandleRequest(b *testing.B) { l := test.NewLogger() myVpnNet := netip.MustParsePrefix("10.128.0.1/0") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) c := config.NewC(l) lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil) @@ -195,12 +197,7 @@ func TestLighthouse_Memory(t *testing.T) { c.Settings["listen"] = map[string]any{"port": 4242} myVpnNet := netip.MustParsePrefix("10.128.0.1/24") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) lh.ifce = &mockEncWriter{} require.NoError(t, err) @@ -280,12 +277,7 @@ func TestLighthouse_reload(t *testing.T) { c.Settings["listen"] = map[string]any{"port": 4242} myVpnNet := netip.MustParsePrefix("10.128.0.1/24") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) @@ -315,12 +307,7 @@ func TestLighthouse_reloadStaticHostMap(t *testing.T) { } myVpnNet := netip.MustParsePrefix("10.128.0.1/24") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) @@ -429,7 +416,9 @@ func TestLighthouse_reloadStaticHostMap(t *testing.T) { assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("3.3.3.3:4242")}, rl.CopyAddrs([]netip.Prefix{})) } -func newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler) testLhReply { +// sendLHHostRequest delivers a HostQuery to lhh and hands back the writer that +// captured what it emitted. Pass a nil filter to see every message. +func sendLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler, filter *NebulaMeta_MessageType) *testEncWriter { req := &NebulaMeta{ Type: NebulaMeta_HostQuery, Details: &NebulaMetaDetails{}, @@ -447,12 +436,59 @@ func newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, l panic(err) } - filter := NebulaMeta_HostQueryReply - w := &testEncWriter{ - metaFilter: &filter, - } + w := &testEncWriter{metaFilter: filter} lhh.HandleRequest(fromAddr, []netip.Addr{myVpnIp}, b, w) - return w.lastReply + return w +} + +func newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler) testLhReply { + filter := NebulaMeta_HostQueryReply + return sendLHHostRequest(fromAddr, myVpnIp, queryVpnIp, lhh, &filter).lastReply +} + +func TestLighthouse_IgnoresHostQueryForItself(t *testing.T) { + // Validate that we don't answer host queries for our own address. + l := test.NewLogger() + + myVpnNet := netip.MustParsePrefix("10.128.0.1/24") + myVpnIp := myVpnNet.Addr() + + c := config.NewC(l) + c.Settings["lighthouse"] = map[string]any{"am_lighthouse": true} + c.Settings["listen"] = map[string]any{"port": 4242} + // Add a static_host_map entry for ourselves, so our address + // is in the addrMap. + c.Settings["static_host_map"] = map[string]any{ + myVpnIp.String(): []any{"192.168.100.1:4242"}, + } + + lh, err := NewLightHouseFromConfig(t.Context(), l, c, testCertState(myVpnNet), nil, nil) + require.NoError(t, err) + lh.ifce = &mockEncWriter{} + lhh := lh.NewRequestHandler() + + peerVpnIp := netip.MustParseAddr("10.128.0.2") + peerUdpAddr := netip.MustParseAddrPort("10.0.0.2:4242") + otherVpnIp := netip.MustParseAddr("10.128.0.3") + otherUdpAddr := netip.MustParseAddrPort("10.0.0.3:4242") + + newLHHostUpdate(peerUdpAddr, peerVpnIp, []netip.AddrPort{peerUdpAddr}, lhh) + newLHHostUpdate(otherUdpAddr, otherVpnIp, []netip.AddrPort{otherUdpAddr}, lhh) + + // Control: a query about a real peer is still answered, and still ends with + // the punch notification aimed at the host that was asked about. + w := sendLHHostRequest(peerUdpAddr, peerVpnIp, otherVpnIp, lhh, nil) + require.NotNil(t, w.lastReply.msg) + assert.Equal(t, NebulaMeta_HostPunchNotification, w.lastReply.msg.Type) + assert.Equal(t, otherVpnIp, w.lastReply.vpnIp) + + // Now validate that we don't send to ourselves. + found, _, err := lh.queryAndPrepMessage(myVpnIp, func(*cache) (int, error) { return 0, nil }) + require.NoError(t, err) + require.True(t, found, "the lighthouse should hold a cache entry for its own address") + + w = sendLHHostRequest(peerUdpAddr, peerVpnIp, myVpnIp, lhh, nil) + assert.Nil(t, w.lastReply.msg, "a query about our own address must produce no reply and no punch notification") } func newLHHostUpdate(fromAddr netip.AddrPort, vpnIp netip.Addr, addrs []netip.AddrPort, lhh *LightHouseHandler) { @@ -642,12 +678,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) { } myVpnNet := netip.MustParsePrefix("10.128.0.1/24") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) lh.ifce = &mockEncWriter{} @@ -708,12 +739,7 @@ func TestLighthouse_DeletesWork(t *testing.T) { } myVpnNet := netip.MustParsePrefix("10.128.0.1/24") - nt := new(bart.Lite) - nt.Insert(myVpnNet) - cs := &CertState{ - myVpnNetworks: []netip.Prefix{myVpnNet}, - myVpnNetworksTable: nt, - } + cs := testCertState(myVpnNet) lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) lh.ifce = &mockEncWriter{}