123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- package nebula
- import (
- "context"
- "encoding/binary"
- "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"
- "gopkg.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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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)
- }
- 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)
- }
|