| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- package nebula
- import (
- "encoding/binary"
- "net/netip"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // trackedHostnameResults builds a *hostnamesResults with a known cancel function and a
- // pre-populated ips map so tests can assert cancellation and verify previously-resolved
- // IPs survive a cancel without spinning up a real DNS resolver.
- func trackedHostnameResults(cancelFn func(), addrs ...string) *hostnamesResults {
- hr := &hostnamesResults{cancelFn: cancelFn}
- ips := map[netip.AddrPort]struct{}{}
- for _, a := range addrs {
- ips[netip.MustParseAddrPort(a)] = struct{}{}
- }
- hr.ips.Store(&ips)
- return hr
- }
- func TestRemoteList_Rebuild(t *testing.T) {
- rl := NewRemoteList([]netip.Addr{netip.MustParseAddr("0.0.0.0")}, nil)
- rl.unlockedSetV4(
- netip.MustParseAddr("0.0.0.0"),
- netip.MustParseAddr("0.0.0.0"),
- []*V4AddrPort{
- newIp4AndPortFromString("70.199.182.92:1475"), // this is duped
- newIp4AndPortFromString("172.17.0.182:10101"),
- newIp4AndPortFromString("172.17.1.1:10101"), // this is duped
- newIp4AndPortFromString("172.18.0.1:10101"), // this is duped
- newIp4AndPortFromString("172.18.0.1:10101"), // this is a dupe
- newIp4AndPortFromString("172.19.0.1:10101"),
- newIp4AndPortFromString("172.31.0.1:10101"),
- newIp4AndPortFromString("172.17.1.1:10101"), // this is a dupe
- newIp4AndPortFromString("70.199.182.92:1476"), // almost dupe of 0 with a diff port
- newIp4AndPortFromString("70.199.182.92:1475"), // this is a dupe
- },
- func(netip.Addr, *V4AddrPort) bool { return true },
- )
- rl.unlockedSetV6(
- netip.MustParseAddr("0.0.0.1"),
- netip.MustParseAddr("0.0.0.1"),
- []*V6AddrPort{
- newIp6AndPortFromString("[1::1]:1"), // this is duped
- newIp6AndPortFromString("[1::1]:2"), // almost dupe of 0 with a diff port, also gets duped
- newIp6AndPortFromString("[1:100::1]:1"),
- newIp6AndPortFromString("[1::1]:1"), // this is a dupe
- newIp6AndPortFromString("[1::1]:2"), // this is a dupe
- },
- func(netip.Addr, *V6AddrPort) bool { return true },
- )
- rl.unlockedSetRelay(
- netip.MustParseAddr("0.0.0.1"),
- []netip.Addr{
- netip.MustParseAddr("1::1"),
- netip.MustParseAddr("1.2.3.4"),
- netip.MustParseAddr("1.2.3.4"),
- netip.MustParseAddr("1::1"),
- },
- )
- rl.Rebuild([]netip.Prefix{})
- assert.Len(t, rl.addrs, 10, "addrs contains too many entries")
- // ipv6 first, sorted lexically within
- assert.Equal(t, "[1::1]:1", rl.addrs[0].String())
- assert.Equal(t, "[1::1]:2", rl.addrs[1].String())
- assert.Equal(t, "[1:100::1]:1", rl.addrs[2].String())
- // ipv4 last, sorted by public first, then private, lexically within them
- assert.Equal(t, "70.199.182.92:1475", rl.addrs[3].String())
- assert.Equal(t, "70.199.182.92:1476", rl.addrs[4].String())
- assert.Equal(t, "172.17.0.182:10101", rl.addrs[5].String())
- assert.Equal(t, "172.17.1.1:10101", rl.addrs[6].String())
- assert.Equal(t, "172.18.0.1:10101", rl.addrs[7].String())
- assert.Equal(t, "172.19.0.1:10101", rl.addrs[8].String())
- assert.Equal(t, "172.31.0.1:10101", rl.addrs[9].String())
- // Now ensure we can hoist ipv4 up
- rl.Rebuild([]netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")})
- assert.Len(t, rl.addrs, 10, "addrs contains too many entries")
- // ipv4 first, public then private, lexically within them
- assert.Equal(t, "70.199.182.92:1475", rl.addrs[0].String())
- assert.Equal(t, "70.199.182.92:1476", rl.addrs[1].String())
- assert.Equal(t, "172.17.0.182:10101", rl.addrs[2].String())
- assert.Equal(t, "172.17.1.1:10101", rl.addrs[3].String())
- assert.Equal(t, "172.18.0.1:10101", rl.addrs[4].String())
- assert.Equal(t, "172.19.0.1:10101", rl.addrs[5].String())
- assert.Equal(t, "172.31.0.1:10101", rl.addrs[6].String())
- // ipv6 last, sorted by public first, then private, lexically within them
- assert.Equal(t, "[1::1]:1", rl.addrs[7].String())
- assert.Equal(t, "[1::1]:2", rl.addrs[8].String())
- assert.Equal(t, "[1:100::1]:1", rl.addrs[9].String())
- // assert relay deduplicated
- assert.Len(t, rl.relays, 2)
- assert.Equal(t, "1.2.3.4", rl.relays[0].String())
- assert.Equal(t, "1::1", rl.relays[1].String())
- // Ensure we can hoist a specific ipv4 range over anything else
- rl.Rebuild([]netip.Prefix{netip.MustParsePrefix("172.17.0.0/16")})
- assert.Len(t, rl.addrs, 10, "addrs contains too many entries")
- // Preferred ipv4 first
- assert.Equal(t, "172.17.0.182:10101", rl.addrs[0].String())
- assert.Equal(t, "172.17.1.1:10101", rl.addrs[1].String())
- // ipv6 next
- assert.Equal(t, "[1::1]:1", rl.addrs[2].String())
- assert.Equal(t, "[1::1]:2", rl.addrs[3].String())
- assert.Equal(t, "[1:100::1]:1", rl.addrs[4].String())
- // the remaining ipv4 last
- assert.Equal(t, "70.199.182.92:1475", rl.addrs[5].String())
- assert.Equal(t, "70.199.182.92:1476", rl.addrs[6].String())
- assert.Equal(t, "172.18.0.1:10101", rl.addrs[7].String())
- assert.Equal(t, "172.19.0.1:10101", rl.addrs[8].String())
- assert.Equal(t, "172.31.0.1:10101", rl.addrs[9].String())
- }
- func TestRemoteList_ResetForOwner(t *testing.T) {
- ourselves := netip.MustParseAddr("10.0.0.1")
- otherOwner := netip.MustParseAddr("10.0.0.2")
- vpnAddr := netip.MustParseAddr("10.0.0.99")
- rl := NewRemoteList([]netip.Addr{vpnAddr}, nil)
- rl.unlockedSetV4(ourselves, vpnAddr,
- []*V4AddrPort{newIp4AndPortFromString("1.1.1.1:4242")},
- func(netip.Addr, *V4AddrPort) bool { return true },
- )
- rl.unlockedSetV6(ourselves, vpnAddr,
- []*V6AddrPort{newIp6AndPortFromString("[1::1]:4242")},
- func(netip.Addr, *V6AddrPort) bool { return true },
- )
- rl.unlockedSetV4(otherOwner, vpnAddr,
- []*V4AddrPort{newIp4AndPortFromString("2.2.2.2:4242")},
- func(netip.Addr, *V4AddrPort) bool { return true },
- )
- canceled := 0
- hr := trackedHostnameResults(func() { canceled++ }, "3.3.3.3:4242")
- rl.Lock()
- rl.unlockedSetHostnamesResults(hr)
- rl.Unlock()
- rl.ResetForOwner(ourselves)
- rl.RLock()
- defer rl.RUnlock()
- assert.Empty(t, rl.cache[ourselves].v4.reported, "our v4 reported should be cleared")
- assert.Empty(t, rl.cache[ourselves].v6.reported, "our v6 reported should be cleared")
- assert.Len(t, rl.cache[otherOwner].v4.reported, 1, "other owner's contribution must be preserved")
- assert.Equal(t, "2.2.2.2:4242", protoV4AddrPortToNetAddrPort(rl.cache[otherOwner].v4.reported[0]).String())
- assert.Equal(t, 1, canceled, "DNS resolution goroutine should be canceled")
- assert.Same(t, hr, rl.hr, "hostnamesResults must be preserved so DNS-resolved IPs keep feeding addrs until replaced")
- assert.NotEmpty(t, rl.hr.GetAddrs(), "previously-resolved IPs should still be readable after cancel")
- assert.True(t, rl.shouldRebuild, "shouldRebuild must be set so the next Rebuild recomputes addrs")
- }
- func TestRemoteList_ResetForOwner_NoEntry(t *testing.T) {
- // An owner with no cache entry must not panic; shouldRebuild is still set and any
- // existing hostnamesResults is canceled.
- rl := NewRemoteList([]netip.Addr{netip.MustParseAddr("10.0.0.99")}, nil)
- canceled := 0
- rl.Lock()
- rl.unlockedSetHostnamesResults(trackedHostnameResults(func() { canceled++ }, "3.3.3.3:4242"))
- rl.Unlock()
- rl.ResetForOwner(netip.MustParseAddr("10.0.0.1"))
- rl.RLock()
- defer rl.RUnlock()
- assert.Equal(t, 1, canceled)
- assert.True(t, rl.shouldRebuild)
- }
- func TestRemoteList_ClearHostnameResults(t *testing.T) {
- rl := NewRemoteList([]netip.Addr{netip.MustParseAddr("10.0.0.99")}, nil)
- canceled := 0
- hr := trackedHostnameResults(func() { canceled++ }, "3.3.3.3:4242")
- rl.Lock()
- rl.unlockedSetHostnamesResults(hr)
- rl.Unlock()
- require.NotEmpty(t, hr.GetAddrs(), "hostnamesResults should have its fastrack IPs populated")
- rl.ClearHostnameResults()
- rl.RLock()
- defer rl.RUnlock()
- assert.Equal(t, 1, canceled, "DNS resolution goroutine should be canceled")
- assert.Nil(t, rl.hr, "hostnamesResults should be dropped")
- assert.True(t, rl.shouldRebuild)
- }
- func BenchmarkFullRebuild(b *testing.B) {
- rl := NewRemoteList([]netip.Addr{netip.MustParseAddr("0.0.0.0")}, nil)
- rl.unlockedSetV4(
- netip.MustParseAddr("0.0.0.0"),
- netip.MustParseAddr("0.0.0.0"),
- []*V4AddrPort{
- newIp4AndPortFromString("70.199.182.92:1475"),
- newIp4AndPortFromString("172.17.0.182:10101"),
- newIp4AndPortFromString("172.17.1.1:10101"),
- newIp4AndPortFromString("172.18.0.1:10101"),
- newIp4AndPortFromString("172.19.0.1:10101"),
- newIp4AndPortFromString("172.31.0.1:10101"),
- newIp4AndPortFromString("172.17.1.1:10101"), // this is a dupe
- newIp4AndPortFromString("70.199.182.92:1476"), // dupe of 0 with a diff port
- },
- func(netip.Addr, *V4AddrPort) bool { return true },
- )
- rl.unlockedSetV6(
- netip.MustParseAddr("0.0.0.0"),
- netip.MustParseAddr("0.0.0.0"),
- []*V6AddrPort{
- newIp6AndPortFromString("[1::1]:1"),
- newIp6AndPortFromString("[1::1]:2"), // dupe of 0 with a diff port
- newIp6AndPortFromString("[1:100::1]:1"),
- newIp6AndPortFromString("[1::1]:1"), // this is a dupe
- },
- func(netip.Addr, *V6AddrPort) bool { return true },
- )
- b.Run("no preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.shouldRebuild = true
- rl.Rebuild([]netip.Prefix{})
- }
- })
- ipNet1 := netip.MustParsePrefix("172.17.0.0/16")
- b.Run("1 preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.shouldRebuild = true
- rl.Rebuild([]netip.Prefix{ipNet1})
- }
- })
- ipNet2 := netip.MustParsePrefix("70.0.0.0/8")
- b.Run("2 preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.shouldRebuild = true
- rl.Rebuild([]netip.Prefix{ipNet2})
- }
- })
- ipNet3 := netip.MustParsePrefix("0.0.0.0/0")
- b.Run("3 preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.shouldRebuild = true
- rl.Rebuild([]netip.Prefix{ipNet1, ipNet2, ipNet3})
- }
- })
- }
- func BenchmarkSortRebuild(b *testing.B) {
- rl := NewRemoteList([]netip.Addr{netip.MustParseAddr("0.0.0.0")}, nil)
- rl.unlockedSetV4(
- netip.MustParseAddr("0.0.0.0"),
- netip.MustParseAddr("0.0.0.0"),
- []*V4AddrPort{
- newIp4AndPortFromString("70.199.182.92:1475"),
- newIp4AndPortFromString("172.17.0.182:10101"),
- newIp4AndPortFromString("172.17.1.1:10101"),
- newIp4AndPortFromString("172.18.0.1:10101"),
- newIp4AndPortFromString("172.19.0.1:10101"),
- newIp4AndPortFromString("172.31.0.1:10101"),
- newIp4AndPortFromString("172.17.1.1:10101"), // this is a dupe
- newIp4AndPortFromString("70.199.182.92:1476"), // dupe of 0 with a diff port
- },
- func(netip.Addr, *V4AddrPort) bool { return true },
- )
- rl.unlockedSetV6(
- netip.MustParseAddr("0.0.0.0"),
- netip.MustParseAddr("0.0.0.0"),
- []*V6AddrPort{
- newIp6AndPortFromString("[1::1]:1"),
- newIp6AndPortFromString("[1::1]:2"), // dupe of 0 with a diff port
- newIp6AndPortFromString("[1:100::1]:1"),
- newIp6AndPortFromString("[1::1]:1"), // this is a dupe
- },
- func(netip.Addr, *V6AddrPort) bool { return true },
- )
- b.Run("no preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.shouldRebuild = true
- rl.Rebuild([]netip.Prefix{})
- }
- })
- ipNet1 := netip.MustParsePrefix("172.17.0.0/16")
- rl.Rebuild([]netip.Prefix{ipNet1})
- b.Run("1 preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.Rebuild([]netip.Prefix{ipNet1})
- }
- })
- ipNet2 := netip.MustParsePrefix("70.0.0.0/8")
- rl.Rebuild([]netip.Prefix{ipNet1, ipNet2})
- b.Run("2 preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.Rebuild([]netip.Prefix{ipNet1, ipNet2})
- }
- })
- ipNet3 := netip.MustParsePrefix("0.0.0.0/0")
- rl.Rebuild([]netip.Prefix{ipNet1, ipNet2, ipNet3})
- b.Run("3 preferred", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- rl.Rebuild([]netip.Prefix{ipNet1, ipNet2, ipNet3})
- }
- })
- }
- func newIp4AndPortFromString(s string) *V4AddrPort {
- a := netip.MustParseAddrPort(s)
- v4Addr := a.Addr().As4()
- return &V4AddrPort{
- Addr: binary.BigEndian.Uint32(v4Addr[:]),
- Port: uint32(a.Port()),
- }
- }
- func newIp6AndPortFromString(s string) *V6AddrPort {
- a := netip.MustParseAddrPort(s)
- v6Addr := a.Addr().As16()
- return &V6AddrPort{
- Hi: binary.BigEndian.Uint64(v6Addr[:8]),
- Lo: binary.BigEndian.Uint64(v6Addr[8:]),
- Port: uint32(a.Port()),
- }
- }
|