mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-07 08:07:42 +00:00
wgengine/magicsock: add a stress test
And add a peerMap validate method that checks its internal invariants. Updates tailscale/corp#3016 Change-Id: I23708e68ed44d81986d9e2be82029d4555547592 Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com> Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
This commit is contained in:
parent
5a60781919
commit
7901289578
@ -9,9 +9,11 @@
|
|||||||
"context"
|
"context"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -1639,3 +1641,125 @@ func epStrings(eps []tailcfg.Endpoint) (ret []string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStressSetNetworkMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
conn := newTestConn(t)
|
||||||
|
t.Cleanup(func() { conn.Close() })
|
||||||
|
var buf tstest.MemLogger
|
||||||
|
conn.logf = buf.Logf
|
||||||
|
|
||||||
|
conn.SetPrivateKey(key.NewNode())
|
||||||
|
|
||||||
|
const npeers = 5
|
||||||
|
present := make([]bool, npeers)
|
||||||
|
allPeers := make([]*tailcfg.Node, npeers)
|
||||||
|
for i := range allPeers {
|
||||||
|
present[i] = true
|
||||||
|
allPeers[i] = &tailcfg.Node{
|
||||||
|
DiscoKey: randDiscoKey(),
|
||||||
|
Key: randNodeKey(),
|
||||||
|
Endpoints: []string{fmt.Sprintf("192.168.1.2:%d", i)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a PRNG seed. If not provided, generate a new one to get extra coverage.
|
||||||
|
seed, err := strconv.ParseUint(os.Getenv("TS_STRESS_SET_NETWORK_MAP_SEED"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
var buf [8]byte
|
||||||
|
crand.Read(buf[:])
|
||||||
|
seed = binary.LittleEndian.Uint64(buf[:])
|
||||||
|
}
|
||||||
|
t.Logf("TS_STRESS_SET_NETWORK_MAP_SEED=%d", seed)
|
||||||
|
prng := rand.New(rand.NewSource(int64(seed)))
|
||||||
|
|
||||||
|
const iters = 1000 // approx 0.5s on an m1 mac
|
||||||
|
for i := 0; i < iters; i++ {
|
||||||
|
for j := 0; j < npeers; j++ {
|
||||||
|
// Randomize which peers are present.
|
||||||
|
if prng.Int()&1 == 0 {
|
||||||
|
present[j] = !present[j]
|
||||||
|
}
|
||||||
|
// Randomize some peer disco keys and node keys.
|
||||||
|
if prng.Int()&1 == 0 {
|
||||||
|
allPeers[j].DiscoKey = randDiscoKey()
|
||||||
|
}
|
||||||
|
if prng.Int()&1 == 0 {
|
||||||
|
allPeers[j].Key = randNodeKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clone existing peers into a new netmap.
|
||||||
|
peers := make([]*tailcfg.Node, 0, len(allPeers))
|
||||||
|
for peerIdx, p := range allPeers {
|
||||||
|
if present[peerIdx] {
|
||||||
|
peers = append(peers, p.Clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set the netmap.
|
||||||
|
conn.SetNetworkMap(&netmap.NetworkMap{
|
||||||
|
Peers: peers,
|
||||||
|
})
|
||||||
|
// Check invariants.
|
||||||
|
if err := conn.peerMap.validate(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randDiscoKey() (k key.DiscoPublic) { return key.NewDisco().Public() }
|
||||||
|
func randNodeKey() (k key.NodePublic) { return key.NewNode().Public() }
|
||||||
|
|
||||||
|
// validate checks m for internal consistency and reports the first error encountered.
|
||||||
|
// It is used in tests only, so it doesn't need to be efficient.
|
||||||
|
func (m *peerMap) validate() error {
|
||||||
|
seenEps := make(map[*endpoint]bool)
|
||||||
|
for pub, pi := range m.byNodeKey {
|
||||||
|
if got := pi.ep.publicKey; got != pub {
|
||||||
|
return fmt.Errorf("byNodeKey[%v].publicKey = %v", pub, got)
|
||||||
|
}
|
||||||
|
if got, want := pi.ep.wgEndpoint, pub.UntypedHexString(); got != want {
|
||||||
|
return fmt.Errorf("byNodeKey[%v].wgEndpoint = %q, want %q", pub, got, want)
|
||||||
|
}
|
||||||
|
if _, ok := seenEps[pi.ep]; ok {
|
||||||
|
return fmt.Errorf("duplicate endpoint present: %v", pi.ep.publicKey)
|
||||||
|
}
|
||||||
|
seenEps[pi.ep] = true
|
||||||
|
for ipp, v := range pi.ipPorts {
|
||||||
|
if !v {
|
||||||
|
return fmt.Errorf("m.byIPPort[%v] is false, expected map to be set-like", ipp)
|
||||||
|
}
|
||||||
|
if got := m.byIPPort[ipp]; got != pi {
|
||||||
|
return fmt.Errorf("m.byIPPort[%v] = %v, want %v", ipp, got, pi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ipp, pi := range m.byIPPort {
|
||||||
|
if !pi.ipPorts[ipp] {
|
||||||
|
return fmt.Errorf("ipPorts[%v] for %v is false", ipp, pi.ep.publicKey)
|
||||||
|
}
|
||||||
|
pi2 := m.byNodeKey[pi.ep.publicKey]
|
||||||
|
if pi != pi2 {
|
||||||
|
return fmt.Errorf("byNodeKey[%v]=%p doesn't match byIPPort[%v]=%p", pi, pi, pi.ep.publicKey, pi2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publicToDisco := make(map[key.NodePublic]key.DiscoPublic)
|
||||||
|
for disco, nodes := range m.nodesOfDisco {
|
||||||
|
for pub, v := range nodes {
|
||||||
|
if !v {
|
||||||
|
return fmt.Errorf("m.nodeOfDisco[%v][%v] is false, expected map to be set-like", disco, pub)
|
||||||
|
}
|
||||||
|
if _, ok := m.byNodeKey[pub]; !ok {
|
||||||
|
return fmt.Errorf("nodesOfDisco refers to public key %v, which is not present in byNodeKey", pub)
|
||||||
|
}
|
||||||
|
if _, ok := publicToDisco[pub]; ok {
|
||||||
|
return fmt.Errorf("publicKey %v refers to multiple disco keys", pub)
|
||||||
|
}
|
||||||
|
publicToDisco[pub] = disco
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user