mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 03:57:03 +02:00
8589b76e1f
localAddrs runs inside every SendUpdate, which fires on lighthouse.interval and again on every network change via RebindUDPServer. Logging the failure there meant a persistent failure warned every interval for the life of the process, once per failing interface. collectLocalAddrs now returns its failures instead of logging them, which keeps it stateless and lets the tests assert on errors rather than log output. The lighthouse holds the previous error and warns only when it changes, demoting repeats to Debug, matching how handshake_manager handles repeated send failures.
772 lines
24 KiB
Go
772 lines
24 KiB
Go
package nebula
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"github.com/gaissmai/bart"
|
|
"github.com/slackhq/nebula/cert"
|
|
"github.com/slackhq/nebula/config"
|
|
"github.com/slackhq/nebula/header"
|
|
"github.com/slackhq/nebula/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.yaml.in/yaml/v3"
|
|
)
|
|
|
|
func TestOldIPv4Only(t *testing.T) {
|
|
// This test ensures our new ipv6 enabled LH protobuf IpAndPorts works with the old style to enable backwards compatibility
|
|
b := []byte{8, 129, 130, 132, 80, 16, 10}
|
|
var m V4AddrPort
|
|
err := m.Unmarshal(b)
|
|
require.NoError(t, err)
|
|
ip := netip.MustParseAddr("10.1.1.1")
|
|
bp := ip.As4()
|
|
assert.Equal(t, binary.BigEndian.Uint32(bp[:]), m.GetAddr())
|
|
}
|
|
|
|
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,
|
|
}
|
|
lh1 := "10.128.0.2"
|
|
|
|
c := config.NewC(l)
|
|
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
|
_, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
lh2 := "10.128.0.3"
|
|
c = config.NewC(l)
|
|
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}}
|
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}}
|
|
_, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
|
}
|
|
|
|
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,
|
|
}
|
|
lh1 := "10.128.0.2"
|
|
|
|
c := config.NewC(l)
|
|
c.Settings["lighthouse"] = map[string]any{
|
|
"hosts": []any{lh1},
|
|
"interval": "1s",
|
|
}
|
|
|
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.NoError(t, err)
|
|
lh.ifce = &mockEncWriter{}
|
|
|
|
// The first one routine is kicked off by main.go currently, lets make sure that one dies
|
|
require.NoError(t, c.ReloadConfigString("lighthouse:\n interval: 5"))
|
|
assert.Equal(t, int64(5), lh.interval.Load())
|
|
|
|
// Subsequent calls are killed off by the LightHouse.Reload function
|
|
require.NoError(t, c.ReloadConfigString("lighthouse:\n interval: 10"))
|
|
assert.Equal(t, int64(10), lh.interval.Load())
|
|
|
|
// If this completes then nothing is stealing our reload routine
|
|
require.NoError(t, c.ReloadConfigString("lighthouse:\n interval: 11"))
|
|
assert.Equal(t, int64(11), lh.interval.Load())
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
c := config.NewC(l)
|
|
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
|
require.NoError(b, err)
|
|
|
|
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
|
hAddr2 := netip.MustParseAddrPort("4.5.6.7:12346")
|
|
|
|
vpnIp3 := netip.MustParseAddr("0.0.0.3")
|
|
lh.addrMap[vpnIp3] = NewRemoteList([]netip.Addr{vpnIp3}, nil)
|
|
lh.addrMap[vpnIp3].unlockedSetV4(
|
|
vpnIp3,
|
|
vpnIp3,
|
|
[]*V4AddrPort{
|
|
netAddrToProtoV4AddrPort(hAddr.Addr(), hAddr.Port()),
|
|
netAddrToProtoV4AddrPort(hAddr2.Addr(), hAddr2.Port()),
|
|
},
|
|
func(netip.Addr, *V4AddrPort) bool { return true },
|
|
)
|
|
|
|
rAddr := netip.MustParseAddrPort("1.2.2.3:12345")
|
|
rAddr2 := netip.MustParseAddrPort("1.2.2.3:12346")
|
|
vpnIp2 := netip.MustParseAddr("0.0.0.3")
|
|
lh.addrMap[vpnIp2] = NewRemoteList([]netip.Addr{vpnIp2}, nil)
|
|
lh.addrMap[vpnIp2].unlockedSetV4(
|
|
vpnIp3,
|
|
vpnIp3,
|
|
[]*V4AddrPort{
|
|
netAddrToProtoV4AddrPort(rAddr.Addr(), rAddr.Port()),
|
|
netAddrToProtoV4AddrPort(rAddr2.Addr(), rAddr2.Port()),
|
|
},
|
|
func(netip.Addr, *V4AddrPort) bool { return true },
|
|
)
|
|
|
|
mw := &mockEncWriter{}
|
|
|
|
hi := []netip.Addr{vpnIp2}
|
|
b.Run("notfound", func(b *testing.B) {
|
|
lhh := lh.NewRequestHandler()
|
|
req := &NebulaMeta{
|
|
Type: NebulaMeta_HostQuery,
|
|
Details: &NebulaMetaDetails{
|
|
OldVpnAddr: 4,
|
|
V4AddrPorts: nil,
|
|
},
|
|
}
|
|
p, err := req.Marshal()
|
|
require.NoError(b, err)
|
|
for n := 0; n < b.N; n++ {
|
|
lhh.HandleRequest(rAddr, hi, p, mw)
|
|
}
|
|
})
|
|
b.Run("found", func(b *testing.B) {
|
|
lhh := lh.NewRequestHandler()
|
|
req := &NebulaMeta{
|
|
Type: NebulaMeta_HostQuery,
|
|
Details: &NebulaMetaDetails{
|
|
OldVpnAddr: 3,
|
|
V4AddrPorts: nil,
|
|
},
|
|
}
|
|
p, err := req.Marshal()
|
|
require.NoError(b, err)
|
|
|
|
for n := 0; n < b.N; n++ {
|
|
lhh.HandleRequest(rAddr, hi, p, mw)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLighthouse_Memory(t *testing.T) {
|
|
l := test.NewLogger()
|
|
|
|
myUdpAddr0 := netip.MustParseAddrPort("10.0.0.2:4242")
|
|
myUdpAddr1 := netip.MustParseAddrPort("192.168.0.2:4242")
|
|
myUdpAddr2 := netip.MustParseAddrPort("172.16.0.2:4242")
|
|
myUdpAddr3 := netip.MustParseAddrPort("100.152.0.2:4242")
|
|
myUdpAddr4 := netip.MustParseAddrPort("24.15.0.2:4242")
|
|
myUdpAddr5 := netip.MustParseAddrPort("192.168.0.2:4243")
|
|
myUdpAddr6 := netip.MustParseAddrPort("192.168.0.2:4244")
|
|
myUdpAddr7 := netip.MustParseAddrPort("192.168.0.2:4245")
|
|
myUdpAddr8 := netip.MustParseAddrPort("192.168.0.2:4246")
|
|
myUdpAddr9 := netip.MustParseAddrPort("192.168.0.2:4247")
|
|
myUdpAddr10 := netip.MustParseAddrPort("192.168.0.2:4248")
|
|
myUdpAddr11 := netip.MustParseAddrPort("192.168.0.2:4249")
|
|
myVpnIp := netip.MustParseAddr("10.128.0.2")
|
|
|
|
theirUdpAddr0 := netip.MustParseAddrPort("10.0.0.3:4242")
|
|
theirUdpAddr1 := netip.MustParseAddrPort("192.168.0.3:4242")
|
|
theirUdpAddr2 := netip.MustParseAddrPort("172.16.0.3:4242")
|
|
theirUdpAddr3 := netip.MustParseAddrPort("100.152.0.3:4242")
|
|
theirUdpAddr4 := netip.MustParseAddrPort("24.15.0.3:4242")
|
|
theirVpnIp := netip.MustParseAddr("10.128.0.3")
|
|
|
|
c := config.NewC(l)
|
|
c.Settings["lighthouse"] = map[string]any{"am_lighthouse": true}
|
|
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,
|
|
}
|
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
lh.ifce = &mockEncWriter{}
|
|
require.NoError(t, err)
|
|
lhh := lh.NewRequestHandler()
|
|
|
|
// Test that my first update responds with just that
|
|
newLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{myUdpAddr1, myUdpAddr2}, lhh)
|
|
r := newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr2)
|
|
|
|
// Ensure we don't accumulate addresses
|
|
newLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{myUdpAddr3}, lhh)
|
|
r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr3)
|
|
|
|
// Grow it back to 2
|
|
newLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{myUdpAddr1, myUdpAddr4}, lhh)
|
|
r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr4)
|
|
|
|
// Update a different host and ask about it
|
|
newLHHostUpdate(theirUdpAddr0, theirVpnIp, []netip.AddrPort{theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4}, lhh)
|
|
r = newLHHostRequest(theirUdpAddr0, theirVpnIp, theirVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4)
|
|
|
|
// Have both hosts ask about the other
|
|
r = newLHHostRequest(theirUdpAddr0, theirVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr4)
|
|
|
|
r = newLHHostRequest(myUdpAddr0, myVpnIp, theirVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4)
|
|
|
|
// Make sure we didn't get changed
|
|
r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr4)
|
|
|
|
// Ensure proper ordering and limiting
|
|
// Send 12 addrs, get 10 back, the last 2 removed, allowing the duplicate to remain (clients dedupe)
|
|
newLHHostUpdate(
|
|
myUdpAddr0,
|
|
myVpnIp,
|
|
[]netip.AddrPort{
|
|
myUdpAddr1,
|
|
myUdpAddr2,
|
|
myUdpAddr3,
|
|
myUdpAddr4,
|
|
myUdpAddr5,
|
|
myUdpAddr5, //Duplicated on purpose
|
|
myUdpAddr6,
|
|
myUdpAddr7,
|
|
myUdpAddr8,
|
|
myUdpAddr9,
|
|
myUdpAddr10,
|
|
myUdpAddr11, // This should get cut
|
|
}, lhh)
|
|
|
|
r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(
|
|
t,
|
|
r.msg.Details.V4AddrPorts,
|
|
myUdpAddr1, myUdpAddr2, myUdpAddr3, myUdpAddr4, myUdpAddr5, myUdpAddr5, myUdpAddr6, myUdpAddr7, myUdpAddr8, myUdpAddr9,
|
|
)
|
|
|
|
// Make sure we won't add ips in our vpn network
|
|
bad1 := netip.MustParseAddrPort("10.128.0.99:4242")
|
|
bad2 := netip.MustParseAddrPort("10.128.0.100:4242")
|
|
good := netip.MustParseAddrPort("1.128.0.99:4242")
|
|
newLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{bad1, bad2, good}, lhh)
|
|
r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
|
|
assertIp4InArray(t, r.msg.Details.V4AddrPorts, good)
|
|
}
|
|
|
|
func TestLighthouse_reload(t *testing.T) {
|
|
l := test.NewLogger()
|
|
c := config.NewC(l)
|
|
c.Settings["lighthouse"] = map[string]any{"am_lighthouse": true}
|
|
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,
|
|
}
|
|
|
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
nc := map[string]any{
|
|
"static_host_map": map[string]any{
|
|
"10.128.0.2": []any{"1.1.1.1:4242"},
|
|
},
|
|
}
|
|
rc, err := yaml.Marshal(nc)
|
|
require.NoError(t, err)
|
|
c.ReloadConfigString(string(rc))
|
|
|
|
err = lh.reload(c, false)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestLighthouse_reloadStaticHostMap verifies that reloading static_host_map applies the new
|
|
// config rather than appending to it. See issue #718.
|
|
func TestLighthouse_reloadStaticHostMap(t *testing.T) {
|
|
l := test.NewLogger()
|
|
c := config.NewC(l)
|
|
c.Settings["lighthouse"] = map[string]any{"am_lighthouse": true}
|
|
c.Settings["listen"] = map[string]any{"port": 4242}
|
|
c.Settings["static_host_map"] = map[string]any{
|
|
"10.128.0.2": []any{"1.1.1.1:4242"},
|
|
}
|
|
|
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
|
nt := new(bart.Lite)
|
|
nt.Insert(myVpnNet)
|
|
cs := &CertState{
|
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
myVpnNetworksTable: nt,
|
|
}
|
|
|
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
staticHost := netip.MustParseAddr("10.128.0.2")
|
|
otherHost := netip.MustParseAddr("10.128.0.3")
|
|
|
|
// Capture the RemoteList pointer up front; an in-flight handshake would hold the same one
|
|
// on hostinfo.remotes, so it must reflect every reload below.
|
|
pinned := lh.Query(staticHost)
|
|
require.NotNil(t, pinned)
|
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("1.1.1.1:4242")}, pinned.CopyAddrs([]netip.Prefix{}))
|
|
|
|
// Replace the remote address. The new address should be the only entry.
|
|
nc := map[string]any{
|
|
"static_host_map": map[string]any{
|
|
"10.128.0.2": []any{"2.2.2.2:4242"},
|
|
},
|
|
}
|
|
rc, err := yaml.Marshal(nc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.ReloadConfigString(string(rc)))
|
|
|
|
rl := lh.Query(staticHost)
|
|
require.NotNil(t, rl)
|
|
assert.Same(t, pinned, rl, "RemoteList pointer must stay stable so in-flight handshakes pick up the change")
|
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("2.2.2.2:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
|
|
|
// Reload back to the original IP. Mirrors the round-trip in issue #718 step 6-8 where
|
|
// the buggy reload produced [1.1.1.1, 2.2.2.2, 1.1.1.1] instead of [1.1.1.1].
|
|
nc = map[string]any{
|
|
"static_host_map": map[string]any{
|
|
"10.128.0.2": []any{"1.1.1.1:4242"},
|
|
},
|
|
}
|
|
rc, err = yaml.Marshal(nc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.ReloadConfigString(string(rc)))
|
|
|
|
rl = lh.Query(staticHost)
|
|
require.NotNil(t, rl)
|
|
assert.Same(t, pinned, rl)
|
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("1.1.1.1:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
|
|
|
// Reload with the same config. An unchanged entry must not duplicate.
|
|
require.NoError(t, c.ReloadConfigString(string(rc)))
|
|
|
|
rl = lh.Query(staticHost)
|
|
require.NotNil(t, rl)
|
|
assert.Same(t, pinned, rl)
|
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("1.1.1.1:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
|
|
|
// Switch back to 2.2.2.2 so the rest of the test continues against a known address.
|
|
nc = map[string]any{
|
|
"static_host_map": map[string]any{
|
|
"10.128.0.2": []any{"2.2.2.2:4242"},
|
|
},
|
|
}
|
|
rc, err = yaml.Marshal(nc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.ReloadConfigString(string(rc)))
|
|
|
|
// Add a second host alongside the first. Both should be present, neither duplicated.
|
|
nc = map[string]any{
|
|
"static_host_map": map[string]any{
|
|
"10.128.0.2": []any{"2.2.2.2:4242"},
|
|
"10.128.0.3": []any{"3.3.3.3:4242"},
|
|
},
|
|
}
|
|
rc, err = yaml.Marshal(nc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.ReloadConfigString(string(rc)))
|
|
|
|
rl = lh.Query(staticHost)
|
|
require.NotNil(t, rl)
|
|
assert.Same(t, pinned, rl, "adding a sibling entry must not displace the existing RemoteList")
|
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("2.2.2.2:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
|
|
|
rl = lh.Query(otherHost)
|
|
require.NotNil(t, rl)
|
|
assert.Equal(t, []netip.AddrPort{netip.MustParseAddrPort("3.3.3.3:4242")}, rl.CopyAddrs([]netip.Prefix{}))
|
|
|
|
// Drop the first host entirely. The vpnAddr is no longer marked static, our owner
|
|
// contribution is cleared, but the addrMap entry stays in place so non-static cache
|
|
// data (from lighthouse queries) on the same RemoteList isn't lost. In-flight handshakes
|
|
// that already had the pointer see an empty address list rather than retrying stale ones.
|
|
nc = map[string]any{
|
|
"static_host_map": map[string]any{
|
|
"10.128.0.3": []any{"3.3.3.3:4242"},
|
|
},
|
|
}
|
|
rc, err = yaml.Marshal(nc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.ReloadConfigString(string(rc)))
|
|
|
|
_, isStatic := lh.GetStaticHostList()[staticHost]
|
|
assert.False(t, isStatic)
|
|
|
|
rl = lh.Query(staticHost)
|
|
require.NotNil(t, rl)
|
|
assert.Same(t, pinned, rl)
|
|
assert.Empty(t, rl.CopyAddrs([]netip.Prefix{}))
|
|
|
|
rl = lh.Query(otherHost)
|
|
require.NotNil(t, rl)
|
|
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 {
|
|
req := &NebulaMeta{
|
|
Type: NebulaMeta_HostQuery,
|
|
Details: &NebulaMetaDetails{},
|
|
}
|
|
|
|
if queryVpnIp.Is4() {
|
|
bip := queryVpnIp.As4()
|
|
req.Details.OldVpnAddr = binary.BigEndian.Uint32(bip[:])
|
|
} else {
|
|
req.Details.VpnAddr = netAddrToProtoAddr(queryVpnIp)
|
|
}
|
|
|
|
b, err := req.Marshal()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
filter := NebulaMeta_HostQueryReply
|
|
w := &testEncWriter{
|
|
metaFilter: &filter,
|
|
}
|
|
lhh.HandleRequest(fromAddr, []netip.Addr{myVpnIp}, b, w)
|
|
return w.lastReply
|
|
}
|
|
|
|
func newLHHostUpdate(fromAddr netip.AddrPort, vpnIp netip.Addr, addrs []netip.AddrPort, lhh *LightHouseHandler) {
|
|
req := &NebulaMeta{
|
|
Type: NebulaMeta_HostUpdateNotification,
|
|
Details: &NebulaMetaDetails{},
|
|
}
|
|
|
|
if vpnIp.Is4() {
|
|
bip := vpnIp.As4()
|
|
req.Details.OldVpnAddr = binary.BigEndian.Uint32(bip[:])
|
|
} else {
|
|
req.Details.VpnAddr = netAddrToProtoAddr(vpnIp)
|
|
}
|
|
|
|
for _, v := range addrs {
|
|
if v.Addr().Is4() {
|
|
req.Details.V4AddrPorts = append(req.Details.V4AddrPorts, netAddrToProtoV4AddrPort(v.Addr(), v.Port()))
|
|
} else {
|
|
req.Details.V6AddrPorts = append(req.Details.V6AddrPorts, netAddrToProtoV6AddrPort(v.Addr(), v.Port()))
|
|
}
|
|
}
|
|
|
|
b, err := req.Marshal()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
w := &testEncWriter{}
|
|
lhh.HandleRequest(fromAddr, []netip.Addr{vpnIp}, b, w)
|
|
}
|
|
|
|
type testLhReply struct {
|
|
nebType header.MessageType
|
|
nebSubType header.MessageSubType
|
|
vpnIp netip.Addr
|
|
msg *NebulaMeta
|
|
}
|
|
|
|
type testEncWriter struct {
|
|
lastReply testLhReply
|
|
metaFilter *NebulaMeta_MessageType
|
|
protocolVersion cert.Version
|
|
}
|
|
|
|
func (tw *testEncWriter) SendVia(via *HostInfo, relay *Relay, ad, nb, out []byte, nocopy bool) {
|
|
}
|
|
func (tw *testEncWriter) Handshake(vpnIp netip.Addr) {
|
|
}
|
|
|
|
func (tw *testEncWriter) SendMessageToHostInfo(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, _, _ []byte) {
|
|
msg := &NebulaMeta{}
|
|
err := msg.Unmarshal(p)
|
|
if tw.metaFilter == nil || msg.Type == *tw.metaFilter {
|
|
tw.lastReply = testLhReply{
|
|
nebType: t,
|
|
nebSubType: st,
|
|
vpnIp: hostinfo.vpnAddrs[0],
|
|
msg: msg,
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (tw *testEncWriter) SendMessageToVpnAddr(t header.MessageType, st header.MessageSubType, vpnIp netip.Addr, p, _, _ []byte) {
|
|
msg := &NebulaMeta{}
|
|
err := msg.Unmarshal(p)
|
|
if tw.metaFilter == nil || msg.Type == *tw.metaFilter {
|
|
tw.lastReply = testLhReply{
|
|
nebType: t,
|
|
nebSubType: st,
|
|
vpnIp: vpnIp,
|
|
msg: msg,
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (tw *testEncWriter) GetHostInfo(vpnIp netip.Addr) *HostInfo {
|
|
return nil
|
|
}
|
|
|
|
func (tw *testEncWriter) GetCertState() *CertState {
|
|
return &CertState{initiatingVersion: tw.protocolVersion}
|
|
}
|
|
|
|
// assertIp4InArray asserts every address in want is at the same position in have and that the lengths match
|
|
func assertIp4InArray(t *testing.T, have []*V4AddrPort, want ...netip.AddrPort) {
|
|
if !assert.Len(t, have, len(want)) {
|
|
return
|
|
}
|
|
|
|
for k, w := range want {
|
|
h := protoV4AddrPortToNetAddrPort(have[k])
|
|
if !(h == w) {
|
|
assert.Fail(t, fmt.Sprintf("Response did not contain: %v at %v, found %v", w, k, h))
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_findNetworkUnion(t *testing.T) {
|
|
var out netip.Addr
|
|
var ok bool
|
|
|
|
tenDot := netip.MustParsePrefix("10.0.0.0/8")
|
|
oneSevenTwo := netip.MustParsePrefix("172.16.0.0/16")
|
|
fe80 := netip.MustParsePrefix("fe80::/8")
|
|
fc00 := netip.MustParsePrefix("fc00::/7")
|
|
|
|
a1 := netip.MustParseAddr("10.0.0.1")
|
|
afe81 := netip.MustParseAddr("fe80::1")
|
|
|
|
//simple
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot}, []netip.Addr{a1})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, a1)
|
|
|
|
//mixed lengths
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot}, []netip.Addr{a1, afe81})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, a1)
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo}, []netip.Addr{a1})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, a1)
|
|
|
|
//mixed family
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{a1})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, a1)
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{a1, afe81})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, a1)
|
|
|
|
//ordering
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{afe81, a1})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, a1)
|
|
out, ok = findNetworkUnion([]netip.Prefix{fe80, tenDot, oneSevenTwo}, []netip.Addr{afe81, a1})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, afe81)
|
|
|
|
//some mismatches
|
|
out, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{afe81})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, afe81)
|
|
out, ok = findNetworkUnion([]netip.Prefix{oneSevenTwo, fe80}, []netip.Addr{a1, afe81})
|
|
assert.True(t, ok)
|
|
assert.Equal(t, out, afe81)
|
|
|
|
//falsey cases
|
|
out, ok = findNetworkUnion([]netip.Prefix{oneSevenTwo, fe80}, []netip.Addr{a1})
|
|
assert.False(t, ok)
|
|
out, ok = findNetworkUnion([]netip.Prefix{fc00, fe80}, []netip.Addr{a1})
|
|
assert.False(t, ok)
|
|
out, ok = findNetworkUnion([]netip.Prefix{oneSevenTwo, fc00}, []netip.Addr{a1, afe81})
|
|
assert.False(t, ok)
|
|
out, ok = findNetworkUnion([]netip.Prefix{fc00}, []netip.Addr{a1, afe81})
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
|
|
l := test.NewLogger()
|
|
|
|
myUdpAddr2 := netip.MustParseAddrPort("1.2.3.4:4242")
|
|
|
|
testSameHostNotStatic := netip.MustParseAddr("10.128.0.41")
|
|
testStaticHost := netip.MustParseAddr("10.128.0.42")
|
|
//myVpnIp := netip.MustParseAddr("10.128.0.2")
|
|
|
|
c := config.NewC(l)
|
|
lh1 := "10.128.0.2"
|
|
c.Settings["lighthouse"] = map[string]any{
|
|
"hosts": []any{lh1},
|
|
"interval": "1s",
|
|
}
|
|
|
|
c.Settings["listen"] = map[string]any{"port": 4242}
|
|
c.Settings["static_host_map"] = map[string]any{
|
|
lh1: []any{"1.1.1.1:4242"},
|
|
"10.128.0.42": []any{"1.2.3.4:4242"},
|
|
}
|
|
|
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
|
nt := new(bart.Lite)
|
|
nt.Insert(myVpnNet)
|
|
cs := &CertState{
|
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
myVpnNetworksTable: nt,
|
|
}
|
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.NoError(t, err)
|
|
lh.ifce = &mockEncWriter{}
|
|
|
|
//test that we actually have the static entry:
|
|
out := lh.Query(testStaticHost)
|
|
assert.NotNil(t, out)
|
|
assert.Equal(t, out.vpnAddrs[0], testStaticHost)
|
|
out.Rebuild([]netip.Prefix{}) //why tho
|
|
assert.Equal(t, out.addrs[0], myUdpAddr2)
|
|
|
|
//bolt on a lower numbered primary IP
|
|
am := lh.unlockedGetRemoteList([]netip.Addr{testStaticHost})
|
|
am.vpnAddrs = []netip.Addr{testSameHostNotStatic, testStaticHost}
|
|
lh.addrMap[testSameHostNotStatic] = am
|
|
out.Rebuild([]netip.Prefix{}) //???
|
|
|
|
//test that we actually have the static entry:
|
|
out = lh.Query(testStaticHost)
|
|
assert.NotNil(t, out)
|
|
assert.Equal(t, out.vpnAddrs[0], testSameHostNotStatic)
|
|
assert.Equal(t, out.vpnAddrs[1], testStaticHost)
|
|
assert.Equal(t, out.addrs[0], myUdpAddr2)
|
|
|
|
//test that we actually have the static entry for BOTH:
|
|
out2 := lh.Query(testSameHostNotStatic)
|
|
assert.Same(t, out2, out)
|
|
|
|
//now do the delete
|
|
lh.DeleteVpnAddrs([]netip.Addr{testSameHostNotStatic, testStaticHost})
|
|
//verify
|
|
out = lh.Query(testSameHostNotStatic)
|
|
assert.NotNil(t, out)
|
|
if out == nil {
|
|
t.Fatal("expected non-nil query for the static host")
|
|
}
|
|
assert.Equal(t, out.vpnAddrs[0], testSameHostNotStatic)
|
|
assert.Equal(t, out.vpnAddrs[1], testStaticHost)
|
|
assert.Equal(t, out.addrs[0], myUdpAddr2)
|
|
}
|
|
|
|
func TestLighthouse_DeletesWork(t *testing.T) {
|
|
l := test.NewLogger()
|
|
|
|
myUdpAddr2 := netip.MustParseAddrPort("1.2.3.4:4242")
|
|
testHost := netip.MustParseAddr("10.128.0.42")
|
|
|
|
c := config.NewC(l)
|
|
lh1 := "10.128.0.2"
|
|
c.Settings["lighthouse"] = map[string]any{
|
|
"hosts": []any{lh1},
|
|
"interval": "1s",
|
|
}
|
|
|
|
c.Settings["listen"] = map[string]any{"port": 4242}
|
|
c.Settings["static_host_map"] = map[string]any{
|
|
lh1: []any{"1.1.1.1:4242"},
|
|
}
|
|
|
|
myVpnNet := netip.MustParsePrefix("10.128.0.1/24")
|
|
nt := new(bart.Lite)
|
|
nt.Insert(myVpnNet)
|
|
cs := &CertState{
|
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
|
myVpnNetworksTable: nt,
|
|
}
|
|
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
|
require.NoError(t, err)
|
|
lh.ifce = &mockEncWriter{}
|
|
|
|
//insert the host
|
|
am := lh.unlockedGetRemoteList([]netip.Addr{testHost})
|
|
am.vpnAddrs = []netip.Addr{testHost}
|
|
am.addrs = []netip.AddrPort{myUdpAddr2}
|
|
lh.addrMap[testHost] = am
|
|
am.Rebuild([]netip.Prefix{}) //???
|
|
|
|
//test that we actually have the entry:
|
|
out := lh.Query(testHost)
|
|
assert.NotNil(t, out)
|
|
assert.Equal(t, out.vpnAddrs[0], testHost)
|
|
out.Rebuild([]netip.Prefix{}) //why tho
|
|
assert.Equal(t, out.addrs[0], myUdpAddr2)
|
|
|
|
//now do the delete
|
|
lh.DeleteVpnAddrs([]netip.Addr{testHost})
|
|
//verify
|
|
out = lh.Query(testHost)
|
|
assert.Nil(t, out)
|
|
}
|
|
|
|
func TestLightHouse_logLocalAddrsErr(t *testing.T) {
|
|
out := &bytes.Buffer{}
|
|
lh := &LightHouse{l: test.NewLoggerWithOutput(out)}
|
|
|
|
// The first sighting of a failure warns.
|
|
lh.logLocalAddrsErr(errors.New("permission denied"))
|
|
assert.Contains(t, out.String(), "level=WARN")
|
|
assert.Contains(t, out.String(), "permission denied")
|
|
|
|
// Repeating unchanged does not warn again, which is what keeps a permanent failure from warning
|
|
// on every lighthouse.interval for the life of the process.
|
|
out.Reset()
|
|
lh.logLocalAddrsErr(errors.New("permission denied"))
|
|
assert.NotContains(t, out.String(), "level=WARN")
|
|
|
|
// A different failure is a new event and warns.
|
|
out.Reset()
|
|
lh.logLocalAddrsErr(errors.New("something else"))
|
|
assert.Contains(t, out.String(), "level=WARN")
|
|
assert.Contains(t, out.String(), "something else")
|
|
|
|
// Recovering resets, so the same failure returning later warns again.
|
|
out.Reset()
|
|
lh.logLocalAddrsErr(nil)
|
|
assert.Empty(t, out.String())
|
|
lh.logLocalAddrsErr(errors.New("something else"))
|
|
assert.Contains(t, out.String(), "level=WARN")
|
|
}
|