mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-26 08:35:17 +00:00
663 lines
24 KiB
Go
663 lines
24 KiB
Go
package identify
|
|
|
|
import (
|
|
crand "crypto/rand"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/libp2p/go-libp2p/core/event"
|
|
"github.com/libp2p/go-libp2p/core/network"
|
|
blankhost "github.com/libp2p/go-libp2p/p2p/host/blank"
|
|
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
|
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
|
|
|
|
ma "github.com/multiformats/go-multiaddr"
|
|
manet "github.com/multiformats/go-multiaddr/net"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newConn(local, remote ma.Multiaddr) *mockConn {
|
|
return &mockConn{local: local, remote: remote}
|
|
}
|
|
|
|
func normalize(addr ma.Multiaddr) ma.Multiaddr {
|
|
for {
|
|
out, last := ma.SplitLast(addr)
|
|
if last == nil {
|
|
return addr
|
|
}
|
|
if _, err := last.ValueForProtocol(ma.P_CERTHASH); err != nil {
|
|
return addr
|
|
}
|
|
addr = out
|
|
}
|
|
}
|
|
|
|
func addrsEqual(a, b []ma.Multiaddr) bool {
|
|
if len(b) != len(a) {
|
|
return false
|
|
}
|
|
for _, x := range b {
|
|
found := false
|
|
for _, y := range a {
|
|
if y.Equal(x) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
for _, x := range a {
|
|
found := false
|
|
for _, y := range b {
|
|
if y.Equal(x) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestObservedAddrManager(t *testing.T) {
|
|
tcp4ListenAddr := ma.StringCast("/ip4/192.168.1.100/tcp/1")
|
|
quic4ListenAddr := ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1")
|
|
webTransport4ListenAddr := ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1/webtransport/certhash/uEgNmb28")
|
|
tcp6ListenAddr := ma.StringCast("/ip6/2004::1/tcp/1")
|
|
quic6ListenAddr := ma.StringCast("/ip6/::/udp/1/quic-v1")
|
|
webTransport6ListenAddr := ma.StringCast("/ip6/::/udp/1/quic-v1/webtransport/certhash/uEgNmb28")
|
|
newObservedAddrMgr := func() *ObservedAddrManager {
|
|
listenAddrs := []ma.Multiaddr{
|
|
tcp4ListenAddr, quic4ListenAddr, webTransport4ListenAddr, tcp6ListenAddr, quic6ListenAddr, webTransport6ListenAddr,
|
|
}
|
|
listenAddrsFunc := func() []ma.Multiaddr {
|
|
return listenAddrs
|
|
}
|
|
interfaceListenAddrsFunc := func() ([]ma.Multiaddr, error) {
|
|
return listenAddrs, nil
|
|
}
|
|
o, err := NewObservedAddrManager(listenAddrsFunc, listenAddrsFunc,
|
|
interfaceListenAddrsFunc, normalize)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return o
|
|
}
|
|
|
|
checkAllEntriesRemoved := func(o *ObservedAddrManager) bool {
|
|
return len(o.Addrs()) == 0 && len(o.externalAddrs) == 0 && len(o.connObservedTWAddrs) == 0 && len(o.localAddrs) == 0
|
|
}
|
|
t.Run("Single Observation", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
observed := ma.StringCast("/ip4/2.2.2.2/tcp/2")
|
|
c1 := newConn(tcp4ListenAddr, ma.StringCast("/ip4/1.2.3.1/tcp/1"))
|
|
c2 := newConn(tcp4ListenAddr, ma.StringCast("/ip4/1.2.3.2/tcp/1"))
|
|
c3 := newConn(tcp4ListenAddr, ma.StringCast("/ip4/1.2.3.3/tcp/1"))
|
|
c4 := newConn(tcp4ListenAddr, ma.StringCast("/ip4/1.2.3.4/tcp/1"))
|
|
o.Record(c1, observed)
|
|
o.Record(c2, observed)
|
|
o.Record(c3, observed)
|
|
o.Record(c4, observed)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observed})
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
o.removeConn(c1)
|
|
o.removeConn(c2)
|
|
o.removeConn(c3)
|
|
o.removeConn(c4)
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("WebTransport inferred from QUIC", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
observedQuic := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1")
|
|
observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport")
|
|
c1 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.1/udp/1/quic-v1"))
|
|
c2 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.2/udp/1/quic-v1"))
|
|
c3 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.3/udp/1/quic-v1/webtransport"))
|
|
c4 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1/webtransport"))
|
|
o.Record(c1, observedQuic)
|
|
o.Record(c2, observedQuic)
|
|
o.Record(c3, observedWebTransport)
|
|
o.Record(c4, observedWebTransport)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedQuic, observedWebTransport})
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
o.removeConn(c1)
|
|
o.removeConn(c2)
|
|
o.removeConn(c3)
|
|
o.removeConn(c4)
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("SameObservers", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
|
|
observedQuic := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1")
|
|
|
|
const N = 4 // ActivationThresh
|
|
var ob1, ob2 [N]connMultiaddrs
|
|
for i := 0; i < N; i++ {
|
|
ob1[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
ob2[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
}
|
|
for i := 0; i < N-1; i++ {
|
|
o.Record(ob1[i], observedQuic)
|
|
o.Record(ob2[i], observedQuic)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
require.Equal(t, o.Addrs(), []ma.Multiaddr{})
|
|
|
|
// We should have a valid address now
|
|
o.Record(ob1[N-1], observedQuic)
|
|
o.Record(ob2[N-1], observedQuic)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedQuic})
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
// Now disconnect first observer group
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(ob1[i])
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
if !addrsEqual(o.Addrs(), []ma.Multiaddr{observedQuic}) {
|
|
t.Fatalf("address removed too earyly %v %v", o.Addrs(), observedQuic)
|
|
}
|
|
|
|
// Now disconnect the second group to check cleanup
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(ob2[i])
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
})
|
|
t.Run("SameObserversDifferentAddrs", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
|
|
observedQuic1 := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1")
|
|
observedQuic2 := ma.StringCast("/ip4/2.2.2.2/udp/3/quic-v1")
|
|
|
|
const N = 4 // ActivationThresh
|
|
var ob1, ob2 [N]connMultiaddrs
|
|
for i := 0; i < N; i++ {
|
|
ob1[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
ob2[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
}
|
|
for i := 0; i < N-1; i++ {
|
|
o.Record(ob1[i], observedQuic1)
|
|
o.Record(ob2[i], observedQuic2)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
require.Equal(t, o.Addrs(), []ma.Multiaddr{})
|
|
|
|
// We should have a valid address now
|
|
o.Record(ob1[N-1], observedQuic1)
|
|
o.Record(ob2[N-1], observedQuic2)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedQuic1, observedQuic2})
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
// Now disconnect first observer group
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(ob1[i])
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
if !addrsEqual(o.Addrs(), []ma.Multiaddr{observedQuic2}) {
|
|
t.Fatalf("address removed too earyly %v %v", o.Addrs(), observedQuic2)
|
|
}
|
|
|
|
// Now disconnect the second group to check cleanup
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(ob2[i])
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("Old observations discarded", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
c1 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.1/udp/1/quic-v1"))
|
|
c2 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.2/udp/1/quic-v1"))
|
|
c3 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.3/udp/1/quic-v1/webtransport"))
|
|
c4 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1/webtransport"))
|
|
var observedQuic, observedWebTransport ma.Multiaddr
|
|
for i := 0; i < 10; i++ {
|
|
// Change the IP address in each observation
|
|
observedQuic = ma.StringCast(fmt.Sprintf("/ip4/2.2.2.%d/udp/2/quic-v1", i))
|
|
observedWebTransport = ma.StringCast(fmt.Sprintf("/ip4/2.2.2.%d/udp/2/quic-v1/webtransport", i))
|
|
o.Record(c1, observedQuic)
|
|
o.Record(c2, observedQuic)
|
|
o.Record(c3, observedWebTransport)
|
|
o.Record(c4, observedWebTransport)
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedQuic, observedWebTransport})
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
|
|
tw, err := thinWaistForm(quic4ListenAddr)
|
|
require.NoError(t, err)
|
|
require.Less(t, len(o.externalAddrs[string(tw.TW.Bytes())]), 2)
|
|
|
|
require.Equal(t, o.AddrsFor(webTransport4ListenAddr), []ma.Multiaddr{observedWebTransport})
|
|
require.Equal(t, o.AddrsFor(quic4ListenAddr), []ma.Multiaddr{observedQuic})
|
|
|
|
o.removeConn(c1)
|
|
o.removeConn(c2)
|
|
o.removeConn(c3)
|
|
o.removeConn(c4)
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("Many connection many observations", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
const N = 100
|
|
var tcpConns, quicConns, webTransportConns [N]*mockConn
|
|
for i := 0; i < N; i++ {
|
|
tcpConns[i] = newConn(tcp4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/tcp/1", i)))
|
|
quicConns[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
webTransportConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i)))
|
|
}
|
|
var observedQuic, observedWebTransport, observedTCP ma.Multiaddr
|
|
for i := 0; i < N; i++ {
|
|
for j := 0; j < 5; j++ {
|
|
// ip addr has the form 2.2.<conn-num>.<obs-num>
|
|
observedQuic = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/udp/2/quic-v1", i/10, j))
|
|
observedWebTransport = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/udp/2/quic-v1/webtransport", i/10, j))
|
|
observedTCP = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/tcp/2", i/10, j))
|
|
o.Record(tcpConns[i], observedTCP)
|
|
o.Record(quicConns[i], observedQuic)
|
|
o.Record(webTransportConns[i], observedWebTransport)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
// At this point we have 10 groups of N / 10 with 10 observations for every connection
|
|
// The output should remain stable
|
|
require.Eventually(t, func() bool {
|
|
return len(o.Addrs()) == 3*maxExternalThinWaistAddrsPerLocalAddr
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
addrs := o.Addrs()
|
|
for i := 0; i < 10; i++ {
|
|
require.ElementsMatch(t, o.Addrs(), addrs, "%s %s", o.Addrs(), addrs)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
// Now we bias a few address counts and check for sorting correctness
|
|
var resTCPAddrs, resQuicAddrs, resWebTransportAddrs [maxExternalThinWaistAddrsPerLocalAddr]ma.Multiaddr
|
|
for i := 0; i < maxExternalThinWaistAddrsPerLocalAddr; i++ {
|
|
resTCPAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/tcp/2", 9-i))
|
|
resQuicAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1", 9-i))
|
|
resWebTransportAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1/webtransport", 9-i))
|
|
o.Record(tcpConns[i], resTCPAddrs[i])
|
|
o.Record(quicConns[i], resQuicAddrs[i])
|
|
o.Record(webTransportConns[i], resWebTransportAddrs[i])
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
var allAddrs []ma.Multiaddr
|
|
allAddrs = append(allAddrs, resTCPAddrs[:]...)
|
|
allAddrs = append(allAddrs, resQuicAddrs[:]...)
|
|
allAddrs = append(allAddrs, resWebTransportAddrs[:]...)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), allAddrs)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(tcpConns[i])
|
|
o.removeConn(quicConns[i])
|
|
o.removeConn(webTransportConns[i])
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("WebTransport certhash", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport")
|
|
c1 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.1/udp/1/quic-v1/webtransport"))
|
|
c2 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.2/udp/1/quic-v1/webtransport"))
|
|
c3 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.3/udp/1/quic-v1/webtransport"))
|
|
c4 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1/webtransport"))
|
|
o.Record(c1, observedWebTransport)
|
|
o.Record(c2, observedWebTransport)
|
|
o.Record(c3, observedWebTransport)
|
|
o.Record(c4, observedWebTransport)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedWebTransport})
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
o.removeConn(c1)
|
|
o.removeConn(c2)
|
|
o.removeConn(c3)
|
|
o.removeConn(c4)
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("getNATType", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
|
|
observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport")
|
|
var udpConns [5 * maxExternalThinWaistAddrsPerLocalAddr]connMultiaddrs
|
|
for i := 0; i < len(udpConns); i++ {
|
|
udpConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i)))
|
|
o.Record(udpConns[i], observedWebTransport)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedWebTransport})
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
|
|
tcpNAT, udpNAT := o.getNATType()
|
|
require.Equal(t, tcpNAT, network.NATDeviceTypeUnknown)
|
|
require.Equal(t, udpNAT, network.NATDeviceTypeCone)
|
|
})
|
|
t.Run("NATTypeSymmetric", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
const N = 100
|
|
var tcpConns, quicConns [N]*mockConn
|
|
for i := 0; i < N; i++ {
|
|
tcpConns[i] = newConn(tcp4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/tcp/1", i)))
|
|
quicConns[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
}
|
|
var observedQuic, observedTCP ma.Multiaddr
|
|
for i := 0; i < N; i++ {
|
|
// ip addr has the form 2.2.<conn-num>.2
|
|
observedQuic = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.2/udp/2/quic-v1", i%20))
|
|
observedTCP = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.2/tcp/2", i%20))
|
|
o.Record(tcpConns[i], observedTCP)
|
|
o.Record(quicConns[i], observedQuic)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
// At this point we have 20 groups with 5 observations for every connection
|
|
// The output should remain stable
|
|
require.Eventually(t, func() bool {
|
|
return len(o.Addrs()) == 2*maxExternalThinWaistAddrsPerLocalAddr
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
|
|
tcpNAT, udpNAT := o.getNATType()
|
|
require.Equal(t, tcpNAT, network.NATDeviceTypeSymmetric)
|
|
require.Equal(t, udpNAT, network.NATDeviceTypeSymmetric)
|
|
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(tcpConns[i])
|
|
o.removeConn(quicConns[i])
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
t.Run("Nill Input", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
o.maybeRecordObservation(nil, nil)
|
|
remoteAddr := ma.StringCast("/ip4/1.2.3.4/tcp/1")
|
|
o.maybeRecordObservation(newConn(tcp4ListenAddr, remoteAddr), nil)
|
|
o.maybeRecordObservation(nil, remoteAddr)
|
|
o.AddrsFor(nil)
|
|
o.removeConn(nil)
|
|
})
|
|
|
|
t.Run("Nat Emitter", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
bus := eventbus.NewBus()
|
|
|
|
s := swarmt.GenSwarm(t, swarmt.EventBus(bus))
|
|
h := blankhost.NewBlankHost(s, blankhost.WithEventBus(bus))
|
|
defer h.Close()
|
|
// make reachability private
|
|
emitter, err := bus.Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful)
|
|
require.NoError(t, err)
|
|
emitter.Emit(event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPrivate})
|
|
|
|
// start nat emitter
|
|
n, err := newNATEmitter(h, o, 10*time.Millisecond)
|
|
require.NoError(t, err)
|
|
defer n.Close()
|
|
|
|
sub, err := bus.Subscribe(new(event.EvtNATDeviceTypeChanged))
|
|
require.NoError(t, err)
|
|
observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport")
|
|
var udpConns [5 * maxExternalThinWaistAddrsPerLocalAddr]connMultiaddrs
|
|
for i := 0; i < len(udpConns); i++ {
|
|
udpConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i)))
|
|
o.Record(udpConns[i], observedWebTransport)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), []ma.Multiaddr{observedWebTransport})
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
|
|
var e interface{}
|
|
select {
|
|
case e = <-sub.Out():
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("expected NAT change event")
|
|
}
|
|
evt := e.(event.EvtNATDeviceTypeChanged)
|
|
require.Equal(t, evt.TransportProtocol, network.NATTransportUDP)
|
|
require.Equal(t, evt.NatDeviceType, network.NATDeviceTypeCone)
|
|
})
|
|
t.Run("Many connection many observations IP4 And IP6", func(t *testing.T) {
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
const N = 100
|
|
var tcp4Conns, quic4Conns, webTransport4Conns [N]*mockConn
|
|
var tcp6Conns, quic6Conns, webTransport6Conns [N]*mockConn
|
|
for i := 0; i < N; i++ {
|
|
tcp4Conns[i] = newConn(tcp4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/tcp/1", i)))
|
|
quic4Conns[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i)))
|
|
webTransport4Conns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i)))
|
|
|
|
tcp6Conns[i] = newConn(tcp6ListenAddr, ma.StringCast(fmt.Sprintf("/ip6/20%02x::/tcp/1", i)))
|
|
quic6Conns[i] = newConn(quic6ListenAddr, ma.StringCast(fmt.Sprintf("/ip6/20%02x::/udp/1/quic-v1", i)))
|
|
webTransport6Conns[i] = newConn(webTransport6ListenAddr, ma.StringCast(fmt.Sprintf("/ip6/20%02x::/udp/1/quic-v1/webtransport", i)))
|
|
}
|
|
var observedQUIC4, observedWebTransport4, observedTCP4 ma.Multiaddr
|
|
var observedQUIC6, observedWebTransport6, observedTCP6 ma.Multiaddr
|
|
for i := 0; i < N; i++ {
|
|
for j := 0; j < 5; j++ {
|
|
// ip addr has the form 2.2.<conn-num>.<obs-num>
|
|
observedQUIC4 = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/udp/2/quic-v1", i/10, j))
|
|
observedWebTransport4 = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/udp/2/quic-v1/webtransport", i/10, j))
|
|
observedTCP4 = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/tcp/2", i/10, j))
|
|
|
|
// ip addr has the form 20XX::YY
|
|
observedQUIC6 = ma.StringCast(fmt.Sprintf("/ip6/20%02x::%02x/udp/2/quic-v1", i/10, j))
|
|
observedWebTransport6 = ma.StringCast(fmt.Sprintf("/ip6/20%02x::%02x/udp/2/quic-v1/webtransport", i/10, j))
|
|
observedTCP6 = ma.StringCast(fmt.Sprintf("/ip6/20%02x::%02x/tcp/2", i/10, j))
|
|
|
|
o.maybeRecordObservation(tcp4Conns[i], observedTCP4)
|
|
o.maybeRecordObservation(quic4Conns[i], observedQUIC4)
|
|
o.maybeRecordObservation(webTransport4Conns[i], observedWebTransport4)
|
|
|
|
o.maybeRecordObservation(tcp6Conns[i], observedTCP6)
|
|
o.maybeRecordObservation(quic6Conns[i], observedQUIC6)
|
|
o.maybeRecordObservation(webTransport6Conns[i], observedWebTransport6)
|
|
}
|
|
}
|
|
// At this point we have 10 groups of N / 10 with 10 observations for every connection
|
|
// The output should remain stable
|
|
require.Eventually(t, func() bool {
|
|
return len(o.Addrs()) == 2*3*maxExternalThinWaistAddrsPerLocalAddr
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
addrs := o.Addrs()
|
|
for i := 0; i < 10; i++ {
|
|
require.ElementsMatch(t, o.Addrs(), addrs, "%s %s", o.Addrs(), addrs)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
// Now we bias a few address counts and check for sorting correctness
|
|
var resTCPAddrs, resQuicAddrs, resWebTransportAddrs []ma.Multiaddr
|
|
|
|
for i, idx := 0, 0; i < maxExternalThinWaistAddrsPerLocalAddr; i++ {
|
|
resTCPAddrs = append(resTCPAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/tcp/2", 9-i)))
|
|
resQuicAddrs = append(resQuicAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1", 9-i)))
|
|
resWebTransportAddrs = append(resWebTransportAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1/webtransport", 9-i)))
|
|
|
|
o.maybeRecordObservation(tcp4Conns[i], resTCPAddrs[idx])
|
|
o.maybeRecordObservation(quic4Conns[i], resQuicAddrs[idx])
|
|
o.maybeRecordObservation(webTransport4Conns[i], resWebTransportAddrs[idx])
|
|
idx++
|
|
|
|
resTCPAddrs = append(resTCPAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/tcp/2", 9-i)))
|
|
resQuicAddrs = append(resQuicAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/udp/2/quic-v1", 9-i)))
|
|
resWebTransportAddrs = append(resWebTransportAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/udp/2/quic-v1/webtransport", 9-i)))
|
|
o.maybeRecordObservation(tcp6Conns[i], resTCPAddrs[idx])
|
|
o.maybeRecordObservation(quic6Conns[i], resQuicAddrs[idx])
|
|
o.maybeRecordObservation(webTransport6Conns[i], resWebTransportAddrs[idx])
|
|
idx++
|
|
}
|
|
var allAddrs []ma.Multiaddr
|
|
allAddrs = append(allAddrs, resTCPAddrs[:]...)
|
|
allAddrs = append(allAddrs, resQuicAddrs[:]...)
|
|
allAddrs = append(allAddrs, resWebTransportAddrs[:]...)
|
|
require.Eventually(t, func() bool {
|
|
return addrsEqual(o.Addrs(), allAddrs)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
|
|
for i := 0; i < N; i++ {
|
|
o.removeConn(tcp4Conns[i])
|
|
o.removeConn(quic4Conns[i])
|
|
o.removeConn(webTransport4Conns[i])
|
|
o.removeConn(tcp6Conns[i])
|
|
o.removeConn(quic6Conns[i])
|
|
o.removeConn(webTransport6Conns[i])
|
|
}
|
|
require.Eventually(t, func() bool {
|
|
return checkAllEntriesRemoved(o)
|
|
}, 1*time.Second, 100*time.Millisecond)
|
|
})
|
|
}
|
|
|
|
func genIPMultiaddr(ip6 bool) ma.Multiaddr {
|
|
var ipB [16]byte
|
|
crand.Read(ipB[:])
|
|
var ip net.IP
|
|
if ip6 {
|
|
ip = net.IP(ipB[:])
|
|
} else {
|
|
ip = net.IP(ipB[:4])
|
|
}
|
|
addr, _ := manet.FromIP(ip)
|
|
return addr
|
|
}
|
|
|
|
func FuzzObservedAddrManager(f *testing.F) {
|
|
protos := []string{
|
|
"/webrtc-direct",
|
|
"/quic-v1",
|
|
"/quic-v1/webtransport",
|
|
}
|
|
tcp4 := ma.StringCast("/ip4/192.168.1.100/tcp/1")
|
|
quic4 := ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1")
|
|
wt4 := ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1/webtransport/certhash/uEgNmb28")
|
|
tcp6 := ma.StringCast("/ip6/1::1/tcp/1")
|
|
quic6 := ma.StringCast("/ip6/::/udp/1/quic-v1")
|
|
wt6 := ma.StringCast("/ip6/::/udp/1/quic-v1/webtransport/certhash/uEgNmb28")
|
|
newObservedAddrMgr := func() *ObservedAddrManager {
|
|
listenAddrs := []ma.Multiaddr{
|
|
tcp4, quic4, wt4, tcp6, quic6, wt6,
|
|
}
|
|
listenAddrsFunc := func() []ma.Multiaddr {
|
|
return listenAddrs
|
|
}
|
|
interfaceListenAddrsFunc := func() ([]ma.Multiaddr, error) {
|
|
return listenAddrs, nil
|
|
}
|
|
o, err := NewObservedAddrManager(listenAddrsFunc, listenAddrsFunc,
|
|
interfaceListenAddrsFunc, normalize)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return o
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, port uint16) {
|
|
addrs := []ma.Multiaddr{genIPMultiaddr(true), genIPMultiaddr(false)}
|
|
n := len(addrs)
|
|
for i := 0; i < n; i++ {
|
|
addrs = append(addrs, addrs[i].Encapsulate(ma.StringCast(fmt.Sprintf("/tcp/%d", port))))
|
|
addrs = append(addrs, addrs[i].Encapsulate(ma.StringCast(fmt.Sprintf("/udp/%d", port))))
|
|
addrs = append(addrs, ma.StringCast(fmt.Sprintf("/tcp/%d", port)))
|
|
addrs = append(addrs, ma.StringCast(fmt.Sprintf("/udp/%d", port)))
|
|
}
|
|
n = len(addrs)
|
|
for i := 0; i < n; i++ {
|
|
for j := 0; j < len(protos); j++ {
|
|
protoAddr := ma.StringCast(protos[j])
|
|
addrs = append(addrs, addrs[i].Encapsulate(protoAddr))
|
|
addrs = append(addrs, protoAddr)
|
|
}
|
|
}
|
|
o := newObservedAddrMgr()
|
|
defer o.Close()
|
|
for i := 0; i < len(addrs); i++ {
|
|
for _, l := range o.listenAddrs() {
|
|
c := newConn(l, addrs[i])
|
|
o.maybeRecordObservation(c, addrs[i])
|
|
o.maybeRecordObservation(c, nil)
|
|
o.maybeRecordObservation(nil, addrs[i])
|
|
o.removeConn(c)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestObserver(t *testing.T) {
|
|
tests := []struct {
|
|
addr ma.Multiaddr
|
|
want string
|
|
}{
|
|
{
|
|
addr: ma.StringCast("/ip4/1.2.3.4/tcp/1"),
|
|
want: "1.2.3.4",
|
|
},
|
|
{
|
|
addr: ma.StringCast("/ip4/192.168.0.1/tcp/1"),
|
|
want: "192.168.0.1",
|
|
},
|
|
{
|
|
addr: ma.StringCast("/ip6/200::1/udp/1/quic-v1"),
|
|
want: "200::",
|
|
},
|
|
{
|
|
addr: ma.StringCast("/ip6/::1/udp/1/quic-v1"),
|
|
want: "::",
|
|
},
|
|
}
|
|
|
|
for i, tc := range tests {
|
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
got, err := getObserver(tc.addr)
|
|
require.NoError(t, err)
|
|
require.Equal(t, got, tc.want)
|
|
})
|
|
}
|
|
}
|