working
This commit is contained in:
		| @@ -1,16 +0,0 @@ | ||||
| # VPPN Peer Code | ||||
|  | ||||
| ## Refactoring for Testability | ||||
|  | ||||
| * [x] connWriter | ||||
| * [x] mcWriter | ||||
| * [x] ifWriter | ||||
| * [ ] ifReader (testing) | ||||
| * [ ] connReader | ||||
| * [ ] mcReader | ||||
| * [ ] hubPoller | ||||
| * [ ] supervisor | ||||
|  | ||||
| ## Updates | ||||
|  | ||||
| * [ ] Send timing info w/ syn/ack packets | ||||
| @@ -1,71 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/netip" | ||||
| 	"runtime/debug" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type pubAddrStore struct { | ||||
| 	lastSeen map[netip.AddrPort]time.Time | ||||
| 	addrList []netip.AddrPort | ||||
| } | ||||
|  | ||||
| func newPubAddrStore() *pubAddrStore { | ||||
| 	return &pubAddrStore{ | ||||
| 		lastSeen: map[netip.AddrPort]time.Time{}, | ||||
| 		addrList: make([]netip.AddrPort, 0, 32), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (store *pubAddrStore) Store(add netip.AddrPort) { | ||||
| 	if localPub { | ||||
| 		log.Printf("OOPS: Local pub but storage attempt: %s", debug.Stack()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !add.IsValid() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if _, exists := store.lastSeen[add]; !exists { | ||||
| 		store.addrList = append(store.addrList, add) | ||||
| 	} | ||||
| 	store.lastSeen[add] = time.Now() | ||||
| 	store.sort() | ||||
| } | ||||
|  | ||||
| func (store *pubAddrStore) Get() (addrs [8]netip.AddrPort) { | ||||
| 	if localPub { | ||||
| 		addrs[0] = localAddr | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	copy(addrs[:], store.addrList) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (store *pubAddrStore) Clean() { | ||||
| 	if localPub { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for ip, lastSeen := range store.lastSeen { | ||||
| 		if time.Since(lastSeen) > timeoutInterval { | ||||
| 			delete(store.lastSeen, ip) | ||||
| 		} | ||||
| 	} | ||||
| 	store.addrList = store.addrList[:0] | ||||
| 	for ip := range store.lastSeen { | ||||
| 		store.addrList = append(store.addrList, ip) | ||||
| 	} | ||||
| 	store.sort() | ||||
| } | ||||
|  | ||||
| func (store *pubAddrStore) sort() { | ||||
| 	sort.Slice(store.addrList, func(i, j int) bool { | ||||
| 		return store.lastSeen[store.addrList[j]].Before(store.lastSeen[store.addrList[i]]) | ||||
| 	}) | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"net/netip" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func TestPubAddrStore(t *testing.T) { | ||||
| 	s := newPubAddrStore() | ||||
|  | ||||
| 	l := []netip.AddrPort{ | ||||
| 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{0, 1, 2, 3}), 20), | ||||
| 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 2, 3}), 21), | ||||
| 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 1, 2, 3}), 22), | ||||
| 	} | ||||
|  | ||||
| 	for i := range l { | ||||
| 		s.Store(l[i]) | ||||
| 		time.Sleep(time.Millisecond) | ||||
| 	} | ||||
|  | ||||
| 	s.Clean() | ||||
|  | ||||
| 	l2 := s.Get() | ||||
| 	if l2[0] != l[2] || l2[1] != l[1] || l2[2] != l[0] { | ||||
| 		t.Fatal(l, l2) | ||||
| 	} | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| const bitSetSize = 512 // Multiple of 64. | ||||
|  | ||||
| type bitSet [bitSetSize / 64]uint64 | ||||
|  | ||||
| func (bs *bitSet) Set(i int) { | ||||
| 	bs[i/64] |= 1 << (i % 64) | ||||
| } | ||||
|  | ||||
| func (bs *bitSet) Clear(i int) { | ||||
| 	bs[i/64] &= ^(1 << (i % 64)) | ||||
| } | ||||
|  | ||||
| func (bs *bitSet) ClearAll() { | ||||
| 	clear(bs[:]) | ||||
| } | ||||
|  | ||||
| func (bs *bitSet) Get(i int) bool { | ||||
| 	return bs[i/64]&(1<<(i%64)) != 0 | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"math/rand" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestBitSet(t *testing.T) { | ||||
| 	state := make([]bool, bitSetSize) | ||||
| 	for i := range state { | ||||
| 		state[i] = rand.Float32() > 0.5 | ||||
| 	} | ||||
|  | ||||
| 	bs := bitSet{} | ||||
|  | ||||
| 	for i := range state { | ||||
| 		if state[i] { | ||||
| 			bs.Set(i) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i := range state { | ||||
| 		if bs.Get(i) != state[i] { | ||||
| 			t.Fatal(i, state[i], bs.Get(i)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i := range state { | ||||
| 		if rand.Float32() > 0.5 { | ||||
| 			state[i] = false | ||||
| 			bs.Clear(i) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i := range state { | ||||
| 		if bs.Get(i) != state[i] { | ||||
| 			t.Fatal(i, state[i], bs.Get(i)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	bs.ClearAll() | ||||
|  | ||||
| 	for i := range state { | ||||
| 		if bs.Get(i) { | ||||
| 			t.Fatal(i, bs.Get(i)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,26 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import "golang.org/x/crypto/nacl/box" | ||||
|  | ||||
| type controlCipher struct { | ||||
| 	sharedKey [32]byte | ||||
| } | ||||
|  | ||||
| func newControlCipher(privKey, pubKey []byte) *controlCipher { | ||||
| 	shared := [32]byte{} | ||||
| 	box.Precompute(&shared, (*[32]byte)(pubKey), (*[32]byte)(privKey)) | ||||
| 	return &controlCipher{shared} | ||||
| } | ||||
|  | ||||
| func (cc *controlCipher) Encrypt(h header, data, out []byte) []byte { | ||||
| 	const s = controlHeaderSize | ||||
| 	out = out[:s+controlCipherOverhead+len(data)] | ||||
| 	h.Marshal(out[:s]) | ||||
| 	box.SealAfterPrecomputation(out[s:s], data, (*[24]byte)(out[:s]), &cc.sharedKey) | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (cc *controlCipher) Decrypt(encrypted, out []byte) (data []byte, ok bool) { | ||||
| 	const s = controlHeaderSize | ||||
| 	return box.OpenAfterPrecomputation(out[:0], encrypted[s:], (*[24]byte)(encrypted[:s]), &cc.sharedKey) | ||||
| } | ||||
| @@ -1,122 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/box" | ||||
| ) | ||||
|  | ||||
| func newControlCipherForTesting() (c1, c2 *controlCipher) { | ||||
| 	pubKey1, privKey1, err := box.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	pubKey2, privKey2, err := box.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	return newControlCipher(privKey1[:], pubKey2[:]), | ||||
| 		newControlCipher(privKey2[:], pubKey1[:]) | ||||
| } | ||||
|  | ||||
| func TestControlCipher(t *testing.T) { | ||||
| 	c1, c2 := newControlCipherForTesting() | ||||
|  | ||||
| 	maxSizePlaintext := make([]byte, bufferSize-controlHeaderSize-controlCipherOverhead) | ||||
| 	rand.Read(maxSizePlaintext) | ||||
|  | ||||
| 	testCases := [][]byte{ | ||||
| 		make([]byte, 0), | ||||
| 		{1}, | ||||
| 		{255}, | ||||
| 		{1, 2, 3, 4, 5}, | ||||
| 		[]byte("Hello world"), | ||||
| 		maxSizePlaintext, | ||||
| 	} | ||||
|  | ||||
| 	for _, plaintext := range testCases { | ||||
| 		h1 := header{ | ||||
| 			StreamID: controlStreamID, | ||||
| 			Counter:  235153, | ||||
| 			SourceIP: 4, | ||||
| 			DestIP:   88, | ||||
| 		} | ||||
|  | ||||
| 		encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 		encrypted = c1.Encrypt(h1, plaintext, encrypted) | ||||
|  | ||||
| 		h2 := header{} | ||||
| 		h2.Parse(encrypted) | ||||
| 		if !reflect.DeepEqual(h1, h2) { | ||||
| 			t.Fatal(h1, h2) | ||||
| 		} | ||||
|  | ||||
| 		decrypted, ok := c2.Decrypt(encrypted, make([]byte, bufferSize)) | ||||
| 		if !ok { | ||||
| 			t.Fatal(ok) | ||||
| 		} | ||||
|  | ||||
| 		if !bytes.Equal(decrypted, plaintext) { | ||||
| 			t.Fatal("not equal") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestControlCipher_ShortCiphertext(t *testing.T) { | ||||
| 	c1, _ := newControlCipherForTesting() | ||||
| 	shortText := make([]byte, controlHeaderSize+controlCipherOverhead-1) | ||||
| 	rand.Read(shortText) | ||||
| 	_, ok := c1.Decrypt(shortText, make([]byte, bufferSize)) | ||||
| 	if ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkControlCipher_Encrypt(b *testing.B) { | ||||
| 	c1, _ := newControlCipherForTesting() | ||||
| 	h1 := header{ | ||||
| 		Counter:  235153, | ||||
| 		SourceIP: 4, | ||||
| 		DestIP:   88, | ||||
| 	} | ||||
|  | ||||
| 	plaintext := make([]byte, bufferSize-controlHeaderSize-controlCipherOverhead) | ||||
| 	rand.Read(plaintext) | ||||
|  | ||||
| 	encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 	b.ResetTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		encrypted = c1.Encrypt(h1, plaintext, encrypted) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkControlCipher_Decrypt(b *testing.B) { | ||||
| 	c1, c2 := newControlCipherForTesting() | ||||
|  | ||||
| 	h1 := header{ | ||||
| 		Counter:  235153, | ||||
| 		SourceIP: 4, | ||||
| 		DestIP:   88, | ||||
| 	} | ||||
|  | ||||
| 	plaintext := make([]byte, bufferSize-controlHeaderSize-controlCipherOverhead) | ||||
| 	rand.Read(plaintext) | ||||
|  | ||||
| 	encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 	encrypted = c1.Encrypt(h1, plaintext, encrypted) | ||||
|  | ||||
| 	decrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 	b.ResetTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		decrypted, _ = c2.Decrypt(encrypted, decrypted) | ||||
| 	} | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"crypto/aes" | ||||
| 	"crypto/cipher" | ||||
| 	"crypto/rand" | ||||
| ) | ||||
|  | ||||
| type dataCipher struct { | ||||
| 	key  [32]byte | ||||
| 	aead cipher.AEAD | ||||
| } | ||||
|  | ||||
| func newDataCipher() *dataCipher { | ||||
| 	key := [32]byte{} | ||||
| 	if _, err := rand.Read(key[:]); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return newDataCipherFromKey(key) | ||||
| } | ||||
|  | ||||
| func newDataCipherFromKey(key [32]byte) *dataCipher { | ||||
| 	block, err := aes.NewCipher(key[:]) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	aead, err := cipher.NewGCM(block) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	return &dataCipher{key: key, aead: aead} | ||||
| } | ||||
|  | ||||
| func (sc *dataCipher) Key() [32]byte { | ||||
| 	return sc.key | ||||
| } | ||||
|  | ||||
| func (sc *dataCipher) Encrypt(h header, data, out []byte) []byte { | ||||
| 	const s = dataHeaderSize | ||||
| 	out = out[:s+dataCipherOverhead+len(data)] | ||||
| 	h.Marshal(out[:s]) | ||||
| 	sc.aead.Seal(out[s:s], out[:s], data, nil) | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (sc *dataCipher) Decrypt(encrypted, out []byte) (data []byte, ok bool) { | ||||
| 	const s = dataHeaderSize | ||||
| 	if len(encrypted) < s+dataCipherOverhead { | ||||
| 		ok = false | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
|  | ||||
| 	data, err = sc.aead.Open(out[:0], encrypted[:s], encrypted[s:], nil) | ||||
| 	ok = err == nil | ||||
| 	return | ||||
| } | ||||
| @@ -1,141 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	mrand "math/rand/v2" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestDataCipher(t *testing.T) { | ||||
| 	maxSizePlaintext := make([]byte, bufferSize-dataHeaderSize-dataCipherOverhead) | ||||
| 	rand.Read(maxSizePlaintext) | ||||
|  | ||||
| 	testCases := [][]byte{ | ||||
| 		make([]byte, 0), | ||||
| 		{1}, | ||||
| 		{255}, | ||||
| 		{1, 2, 3, 4, 5}, | ||||
| 		[]byte("Hello world"), | ||||
| 		maxSizePlaintext, | ||||
| 	} | ||||
|  | ||||
| 	for _, plaintext := range testCases { | ||||
| 		h1 := header{ | ||||
| 			StreamID: dataStreamID, | ||||
| 			Counter:  235153, | ||||
| 			SourceIP: 4, | ||||
| 			DestIP:   88, | ||||
| 		} | ||||
|  | ||||
| 		encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 		dc1 := newDataCipher() | ||||
| 		encrypted = dc1.Encrypt(h1, plaintext, encrypted) | ||||
| 		h2 := header{} | ||||
| 		h2.Parse(encrypted) | ||||
|  | ||||
| 		dc2 := newDataCipherFromKey(dc1.Key()) | ||||
|  | ||||
| 		decrypted, ok := dc2.Decrypt(encrypted, make([]byte, bufferSize-dataHeaderSize)) | ||||
| 		if !ok { | ||||
| 			t.Fatal(ok) | ||||
| 		} | ||||
|  | ||||
| 		if !bytes.Equal(plaintext, decrypted) { | ||||
| 			t.Fatal("not equal") | ||||
| 		} | ||||
|  | ||||
| 		if !reflect.DeepEqual(h1, h2) { | ||||
| 			t.Fatalf("%v != %v", h1, h2) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDataCipher_ModifyCiphertext(t *testing.T) { | ||||
| 	maxSizePlaintext := make([]byte, bufferSize-dataHeaderSize-dataCipherOverhead) | ||||
| 	rand.Read(maxSizePlaintext) | ||||
|  | ||||
| 	testCases := [][]byte{ | ||||
| 		make([]byte, 0), | ||||
| 		{1}, | ||||
| 		{255}, | ||||
| 		{1, 2, 3, 4, 5}, | ||||
| 		[]byte("Hello world"), | ||||
| 		maxSizePlaintext, | ||||
| 	} | ||||
|  | ||||
| 	for _, plaintext := range testCases { | ||||
| 		h1 := header{ | ||||
| 			Counter:  235153, | ||||
| 			SourceIP: 4, | ||||
| 			DestIP:   88, | ||||
| 		} | ||||
|  | ||||
| 		encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 		dc1 := newDataCipher() | ||||
| 		encrypted = dc1.Encrypt(h1, plaintext, encrypted) | ||||
| 		encrypted[mrand.IntN(len(encrypted))]++ | ||||
|  | ||||
| 		dc2 := newDataCipherFromKey(dc1.Key()) | ||||
|  | ||||
| 		_, ok := dc2.Decrypt(encrypted, make([]byte, bufferSize-dataHeaderSize)) | ||||
| 		if ok { | ||||
| 			t.Fatal(ok) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDataCipher_ShortCiphertext(t *testing.T) { | ||||
| 	dc1 := newDataCipher() | ||||
| 	shortText := make([]byte, dataHeaderSize+dataCipherOverhead-1) | ||||
| 	rand.Read(shortText) | ||||
| 	_, ok := dc1.Decrypt(shortText, make([]byte, bufferSize)) | ||||
| 	if ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkDataCipher_Encrypt(b *testing.B) { | ||||
| 	h1 := header{ | ||||
| 		Counter:  235153, | ||||
| 		SourceIP: 4, | ||||
| 		DestIP:   88, | ||||
| 	} | ||||
|  | ||||
| 	plaintext := make([]byte, bufferSize-dataHeaderSize-dataCipherOverhead) | ||||
| 	rand.Read(plaintext) | ||||
|  | ||||
| 	encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 	dc1 := newDataCipher() | ||||
| 	b.ResetTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		encrypted = dc1.Encrypt(h1, plaintext, encrypted) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkDataCipher_Decrypt(b *testing.B) { | ||||
| 	h1 := header{ | ||||
| 		Counter:  235153, | ||||
| 		SourceIP: 4, | ||||
| 		DestIP:   88, | ||||
| 	} | ||||
|  | ||||
| 	plaintext := make([]byte, bufferSize-dataHeaderSize-dataCipherOverhead) | ||||
| 	rand.Read(plaintext) | ||||
|  | ||||
| 	encrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 	dc1 := newDataCipher() | ||||
| 	encrypted = dc1.Encrypt(h1, plaintext, encrypted) | ||||
|  | ||||
| 	decrypted := make([]byte, bufferSize) | ||||
|  | ||||
| 	b.ResetTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		decrypted, _ = dc1.Decrypt(encrypted, decrypted) | ||||
| 	} | ||||
| } | ||||
| @@ -1,13 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| /* | ||||
| func signData(privKey *[64]byte, h header, data, out []byte) []byte { | ||||
| 	out = out[:headerSize] | ||||
| 	h.Marshal(out) | ||||
| 	return sign.Sign(out, data, privKey) | ||||
| } | ||||
|  | ||||
| func openData(pubKey *[32]byte, signed, out []byte) (data []byte, ok bool) { | ||||
| 	return sign.Open(out[:0], signed[headerSize:], pubKey) | ||||
| } | ||||
| */ | ||||
| @@ -1,11 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import "vppn/m" | ||||
|  | ||||
| type localConfig struct { | ||||
| 	m.PeerConfig | ||||
| 	PubKey      []byte | ||||
| 	PrivKey     []byte | ||||
| 	PubSignKey  []byte | ||||
| 	PrivSignKey []byte | ||||
| } | ||||
| @@ -1,3 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| @@ -1,146 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/netip" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type peerRoute struct { | ||||
| 	IP            byte | ||||
| 	Up            bool // True if data can be sent on the route. | ||||
| 	Relay         bool // True if the peer is a relay. | ||||
| 	Direct        bool // True if this is a direct connection. | ||||
| 	PubSignKey    []byte | ||||
| 	ControlCipher *controlCipher | ||||
| 	DataCipher    *dataCipher | ||||
| 	RemoteAddr    netip.AddrPort // Remote address if directly connected. | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type udpAddrPortWriter interface { | ||||
| 	WriteToUDPAddrPort([]byte, netip.AddrPort) (int, error) | ||||
| } | ||||
|  | ||||
| type marshaller interface { | ||||
| 	Marshal([]byte) []byte | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type connWriter struct { | ||||
| 	localIP byte | ||||
| 	conn    udpAddrPortWriter | ||||
|  | ||||
| 	// For sending control packets. | ||||
| 	cBuf1 []byte | ||||
| 	cBuf2 []byte | ||||
|  | ||||
| 	// For sending data packets. | ||||
| 	dBuf1 []byte | ||||
| 	dBuf2 []byte | ||||
|  | ||||
| 	counters [256]uint64 | ||||
|  | ||||
| 	// Lock around for sending on UDP Conn. | ||||
| 	wLock sync.Mutex | ||||
| } | ||||
|  | ||||
| func newConnWriter(conn udpAddrPortWriter, localIP byte) *connWriter { | ||||
| 	w := &connWriter{ | ||||
| 		localIP: localIP, | ||||
| 		conn:    conn, | ||||
| 		cBuf1:   make([]byte, bufferSize), | ||||
| 		cBuf2:   make([]byte, bufferSize), | ||||
| 		dBuf1:   make([]byte, bufferSize), | ||||
| 		dBuf2:   make([]byte, bufferSize), | ||||
| 	} | ||||
| 	for i := range w.counters { | ||||
| 		w.counters[i] = uint64(time.Now().Unix()<<30 + 1) | ||||
| 	} | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| // Not safe for concurrent use. Should only be called by supervisor. | ||||
| func (w *connWriter) SendControlPacket(pkt marshaller, route *peerRoute) { | ||||
| 	buf := w.encryptControlPacket(pkt, route) | ||||
| 	w.writeTo(buf, route.RemoteAddr) | ||||
| } | ||||
|  | ||||
| // Relay control packet. Routes must not be nil. | ||||
| func (w *connWriter) RelayControlPacket(pkt marshaller, route, relay *peerRoute) { | ||||
| 	buf := w.encryptControlPacket(pkt, route) | ||||
| 	w.relayPacket(buf, w.cBuf1, route, relay) | ||||
| } | ||||
|  | ||||
| // Encrypted packet will occupy cBuf2. | ||||
| func (w *connWriter) encryptControlPacket(pkt marshaller, route *peerRoute) []byte { | ||||
| 	buf := pkt.Marshal(w.cBuf1) | ||||
| 	h := header{ | ||||
| 		StreamID: controlStreamID, | ||||
| 		Counter:  atomic.AddUint64(&w.counters[route.IP], 1), | ||||
| 		SourceIP: w.localIP, | ||||
| 		DestIP:   route.IP, | ||||
| 	} | ||||
| 	return route.ControlCipher.Encrypt(h, buf, w.cBuf2) | ||||
| } | ||||
|  | ||||
| // Not safe for concurrent use. Should only be called by ifReader. | ||||
| func (w *connWriter) SendDataPacket(pkt []byte, route *peerRoute) { | ||||
| 	h := header{ | ||||
| 		StreamID: dataStreamID, | ||||
| 		Counter:  atomic.AddUint64(&w.counters[route.IP], 1), | ||||
| 		SourceIP: w.localIP, | ||||
| 		DestIP:   route.IP, | ||||
| 	} | ||||
|  | ||||
| 	enc := route.DataCipher.Encrypt(h, pkt, w.dBuf1) | ||||
| 	w.writeTo(enc, route.RemoteAddr) | ||||
| } | ||||
|  | ||||
| // Relay a data packet. Routes must not be nil. | ||||
| func (w *connWriter) RelayDataPacket(pkt []byte, route, relay *peerRoute) { | ||||
| 	h := header{ | ||||
| 		StreamID: dataStreamID, | ||||
| 		Counter:  atomic.AddUint64(&w.counters[route.IP], 1), | ||||
| 		SourceIP: w.localIP, | ||||
| 		DestIP:   route.IP, | ||||
| 	} | ||||
|  | ||||
| 	enc := route.DataCipher.Encrypt(h, pkt, w.dBuf1) | ||||
| 	w.relayPacket(enc, w.dBuf2, route, relay) | ||||
| } | ||||
|  | ||||
| // Safe for concurrent use. Should only be called by connReader. | ||||
| // | ||||
| // This function will send pkt to the peer directly. This is used when a peer | ||||
| // is acting as a relay and is forwarding already encrypted data for another | ||||
| // peer. | ||||
| func (w *connWriter) SendEncryptedDataPacket(pkt []byte, route *peerRoute) { | ||||
| 	w.writeTo(pkt, route.RemoteAddr) | ||||
| } | ||||
|  | ||||
| func (w *connWriter) relayPacket(data, buf []byte, route, relay *peerRoute) { | ||||
| 	h := header{ | ||||
| 		StreamID: dataStreamID, | ||||
| 		Counter:  atomic.AddUint64(&w.counters[relay.IP], 1), | ||||
| 		SourceIP: w.localIP, | ||||
| 		DestIP:   route.IP, | ||||
| 	} | ||||
|  | ||||
| 	enc := relay.DataCipher.Encrypt(h, data, buf) | ||||
| 	w.writeTo(enc, relay.RemoteAddr) | ||||
| } | ||||
|  | ||||
| func (w *connWriter) writeTo(packet []byte, addr netip.AddrPort) { | ||||
| 	w.wLock.Lock() | ||||
| 	if _, err := w.conn.WriteToUDPAddrPort(packet, addr); err != nil { | ||||
| 		log.Printf("Failed to write to UDP port: %v", err) | ||||
| 	} | ||||
| 	w.wLock.Unlock() | ||||
| } | ||||
| @@ -1,248 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"net/netip" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type testUDPPacket struct { | ||||
| 	Addr netip.AddrPort | ||||
| 	Data []byte | ||||
| } | ||||
|  | ||||
| type testUDPAddrPortWriter struct { | ||||
| 	written []testUDPPacket | ||||
| } | ||||
|  | ||||
| func (w *testUDPAddrPortWriter) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) { | ||||
| 	w.written = append(w.written, testUDPPacket{ | ||||
| 		Addr: addr, | ||||
| 		Data: bytes.Clone(b), | ||||
| 	}) | ||||
| 	return len(b), nil | ||||
| } | ||||
|  | ||||
| func (w *testUDPAddrPortWriter) Written() []testUDPPacket { | ||||
| 	out := w.written | ||||
| 	w.written = []testUDPPacket{} | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type testPacket string | ||||
|  | ||||
| func (p testPacket) Marshal(b []byte) []byte { | ||||
| 	b = b[:len(p)] | ||||
| 	copy(b, []byte(p)) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func testConnWriter_getTestRoutes() (local, remote, relayLocal, relayRemote *peerRoute) { | ||||
| 	localKeys := generateKeys() | ||||
| 	remoteKeys := generateKeys() | ||||
|  | ||||
| 	local = &peerRoute{ | ||||
| 		IP:            2, | ||||
| 		Up:            true, | ||||
| 		Relay:         false, | ||||
| 		PubSignKey:    remoteKeys.PubSignKey, | ||||
| 		ControlCipher: newControlCipher(localKeys.PrivKey, remoteKeys.PubKey), | ||||
| 		DataCipher:    newDataCipher(), | ||||
| 		RemoteAddr:    netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 2}), 100), | ||||
| 	} | ||||
|  | ||||
| 	remote = &peerRoute{ | ||||
| 		IP:            1, | ||||
| 		Up:            true, | ||||
| 		Relay:         false, | ||||
| 		PubSignKey:    localKeys.PubSignKey, | ||||
| 		ControlCipher: newControlCipher(remoteKeys.PrivKey, localKeys.PubKey), | ||||
| 		DataCipher:    local.DataCipher, | ||||
| 		RemoteAddr:    netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), 100), | ||||
| 	} | ||||
|  | ||||
| 	rLocalKeys := generateKeys() | ||||
| 	rRemoteKeys := generateKeys() | ||||
|  | ||||
| 	relayLocal = &peerRoute{ | ||||
| 		IP:            3, | ||||
| 		Up:            true, | ||||
| 		Relay:         true, | ||||
| 		Direct:        true, | ||||
| 		PubSignKey:    rRemoteKeys.PubSignKey, | ||||
| 		ControlCipher: newControlCipher(rLocalKeys.PrivKey, rRemoteKeys.PubKey), | ||||
| 		DataCipher:    newDataCipher(), | ||||
| 		RemoteAddr:    netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 3}), 100), | ||||
| 	} | ||||
|  | ||||
| 	relayRemote = &peerRoute{ | ||||
| 		IP:            1, | ||||
| 		Up:            true, | ||||
| 		Relay:         false, | ||||
| 		Direct:        true, | ||||
| 		PubSignKey:    rLocalKeys.PubSignKey, | ||||
| 		ControlCipher: newControlCipher(rRemoteKeys.PrivKey, rLocalKeys.PubKey), | ||||
| 		DataCipher:    relayLocal.DataCipher, | ||||
| 		RemoteAddr:    netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), 100), | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // Testing if we can send a control packet directly to the remote route. | ||||
| func TestConnWriter_SendControlPacket_direct(t *testing.T) { | ||||
| 	route, rRoute, _, _ := testConnWriter_getTestRoutes() | ||||
| 	route.Direct = true | ||||
|  | ||||
| 	writer := &testUDPAddrPortWriter{} | ||||
| 	w := newConnWriter(writer, rRoute.IP) | ||||
| 	in := testPacket("hello world!") | ||||
|  | ||||
| 	w.SendControlPacket(in, route) | ||||
| 	out := writer.Written() | ||||
| 	if len(out) != 1 { | ||||
| 		t.Fatal(out) | ||||
| 	} | ||||
|  | ||||
| 	if out[0].Addr != route.RemoteAddr { | ||||
| 		t.Fatal(out[0]) | ||||
| 	} | ||||
|  | ||||
| 	dec, ok := rRoute.ControlCipher.Decrypt(out[0].Data, make([]byte, 1024)) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
| 	if string(dec) != string(in) { | ||||
| 		t.Fatal(dec) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing if we can relay a packet via an intermediary. | ||||
| func TestConnWriter_RelayControlPacket_relay(t *testing.T) { | ||||
| 	route, rRoute, relay, rRelay := testConnWriter_getTestRoutes() | ||||
|  | ||||
| 	writer := &testUDPAddrPortWriter{} | ||||
| 	w := newConnWriter(writer, rRoute.IP) | ||||
| 	in := testPacket("hello world!") | ||||
|  | ||||
| 	w.RelayControlPacket(in, route, relay) | ||||
|  | ||||
| 	out := writer.Written() | ||||
| 	if len(out) != 1 { | ||||
| 		t.Fatal(out) | ||||
| 	} | ||||
|  | ||||
| 	if out[0].Addr != relay.RemoteAddr { | ||||
| 		t.Fatal(out[0]) | ||||
| 	} | ||||
|  | ||||
| 	dec, ok := rRelay.DataCipher.Decrypt(out[0].Data, make([]byte, 1024)) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
|  | ||||
| 	dec2, ok := rRoute.ControlCipher.Decrypt(dec, make([]byte, 1024)) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
|  | ||||
| 	if string(dec2) != string(in) { | ||||
| 		t.Fatal(dec2) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing that we can send a data packet directly to a remote route. | ||||
| func TestConnWriter_SendDataPacket_direct(t *testing.T) { | ||||
| 	route, rRoute, _, _ := testConnWriter_getTestRoutes() | ||||
| 	route.Direct = true | ||||
|  | ||||
| 	writer := &testUDPAddrPortWriter{} | ||||
| 	w := newConnWriter(writer, rRoute.IP) | ||||
|  | ||||
| 	in := []byte("hello world!") | ||||
| 	w.SendDataPacket(in, route) | ||||
|  | ||||
| 	out := writer.Written() | ||||
| 	if len(out) != 1 { | ||||
| 		t.Fatal(out) | ||||
| 	} | ||||
|  | ||||
| 	if out[0].Addr != route.RemoteAddr { | ||||
| 		t.Fatal(out[0]) | ||||
| 	} | ||||
|  | ||||
| 	dec, ok := rRoute.DataCipher.Decrypt(out[0].Data, make([]byte, 1024)) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
|  | ||||
| 	if !bytes.Equal(dec, in) { | ||||
| 		t.Fatal(dec) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing that we can relay a data packet via a relay. | ||||
| func TestConnWriter_RelayDataPacket_relay(t *testing.T) { | ||||
| 	route, rRoute, relay, rRelay := testConnWriter_getTestRoutes() | ||||
|  | ||||
| 	writer := &testUDPAddrPortWriter{} | ||||
| 	w := newConnWriter(writer, rRoute.IP) | ||||
| 	in := []byte("Hello world!") | ||||
|  | ||||
| 	w.RelayDataPacket(in, route, relay) | ||||
|  | ||||
| 	out := writer.Written() | ||||
| 	if len(out) != 1 { | ||||
| 		t.Fatal(out) | ||||
| 	} | ||||
|  | ||||
| 	if out[0].Addr != relay.RemoteAddr { | ||||
| 		t.Fatal(out[0]) | ||||
| 	} | ||||
|  | ||||
| 	dec, ok := rRelay.DataCipher.Decrypt(out[0].Data, make([]byte, 1024)) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
|  | ||||
| 	dec2, ok := rRoute.DataCipher.Decrypt(dec, make([]byte, 1024)) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
|  | ||||
| 	if !bytes.Equal(dec2, in) { | ||||
| 		t.Fatal(dec2) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing that we can send an already encrypted packet. | ||||
| func TestConnWriter_SendEncryptedDataPacket(t *testing.T) { | ||||
| 	route, rRoute, _, _ := testConnWriter_getTestRoutes() | ||||
|  | ||||
| 	writer := &testUDPAddrPortWriter{} | ||||
| 	w := newConnWriter(writer, rRoute.IP) | ||||
| 	in := []byte("Hello world!") | ||||
|  | ||||
| 	w.SendEncryptedDataPacket(in, route) | ||||
|  | ||||
| 	out := writer.Written() | ||||
| 	if len(out) != 1 { | ||||
| 		t.Fatal(out) | ||||
| 	} | ||||
|  | ||||
| 	if out[0].Addr != route.RemoteAddr { | ||||
| 		t.Fatal(out[0]) | ||||
| 	} | ||||
|  | ||||
| 	if !bytes.Equal(out[0].Data, in) { | ||||
| 		t.Fatal(out[0]) | ||||
| 	} | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"log" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/box" | ||||
| 	"golang.org/x/crypto/nacl/sign" | ||||
| ) | ||||
|  | ||||
| type cryptoKeys struct { | ||||
| 	PubKey      []byte | ||||
| 	PrivKey     []byte | ||||
| 	PubSignKey  []byte | ||||
| 	PrivSignKey []byte | ||||
| } | ||||
|  | ||||
| func generateKeys() cryptoKeys { | ||||
| 	pubKey, privKey, err := box.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to generate encryption keys: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	pubSignKey, privSignKey, err := sign.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to generate signing keys: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return cryptoKeys{pubKey[:], privKey[:], pubSignKey[:], privSignKey[:]} | ||||
| } | ||||
| @@ -1,14 +0,0 @@ | ||||
| digraph d { | ||||
|     ifReader   -> connWriter; | ||||
|     connReader -> ifWriter; | ||||
|     connReader -> connWriter; | ||||
|     connReader -> supervisor; | ||||
|     mcReader   -> supervisor; | ||||
|     supervisor -> connWriter; | ||||
|     supervisor -> mcWriter; | ||||
|     hubPoller  -> supervisor; | ||||
|  | ||||
|     connWriter [shape="box"]; | ||||
|     mcWriter [shape="box"]; | ||||
|     ifWriter [shape="box"]; | ||||
| } | ||||
| @@ -1,76 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| type dupCheck struct { | ||||
| 	bitSet | ||||
| 	head        int | ||||
| 	tail        int | ||||
| 	headCounter uint64 | ||||
| 	tailCounter uint64 // Also next expected counter value. | ||||
| } | ||||
|  | ||||
| func newDupCheck(headCounter uint64) *dupCheck { | ||||
| 	return &dupCheck{ | ||||
| 		headCounter: headCounter, | ||||
| 		tailCounter: headCounter + 1, | ||||
| 		tail:        1, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (dc *dupCheck) IsDup(counter uint64) bool { | ||||
|  | ||||
| 	// Before head => it's late, say it's a dup. | ||||
| 	if counter < dc.headCounter { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// It's within the counter bounds. | ||||
| 	if counter < dc.tailCounter { | ||||
| 		index := (int(counter-dc.headCounter) + dc.head) % bitSetSize | ||||
| 		if dc.Get(index) { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 		dc.Set(index) | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// It's more than 1 beyond the tail. | ||||
| 	delta := counter - dc.tailCounter | ||||
|  | ||||
| 	// Full clear. | ||||
| 	if delta >= bitSetSize-1 { | ||||
| 		dc.ClearAll() | ||||
| 		dc.Set(0) | ||||
|  | ||||
| 		dc.tail = 1 | ||||
| 		dc.head = 2 | ||||
| 		dc.tailCounter = counter + 1 | ||||
| 		dc.headCounter = dc.tailCounter - bitSetSize + 1 | ||||
|  | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// Clear if necessary. | ||||
| 	for i := 0; i < int(delta); i++ { | ||||
| 		dc.put(false) | ||||
| 	} | ||||
|  | ||||
| 	dc.put(true) | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (dc *dupCheck) put(set bool) { | ||||
| 	if set { | ||||
| 		dc.Set(dc.tail) | ||||
| 	} else { | ||||
| 		dc.Clear(dc.tail) | ||||
| 	} | ||||
|  | ||||
| 	dc.tail = (dc.tail + 1) % bitSetSize | ||||
| 	dc.tailCounter++ | ||||
|  | ||||
| 	if dc.head == dc.tail { | ||||
| 		dc.head = (dc.head + 1) % bitSetSize | ||||
| 		dc.headCounter++ | ||||
| 	} | ||||
| } | ||||
| @@ -1,54 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestDupCheck(t *testing.T) { | ||||
| 	dc := newDupCheck(0) | ||||
|  | ||||
| 	for i := range bitSetSize { | ||||
| 		if dc.IsDup(uint64(i)) { | ||||
| 			t.Fatal("!") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	type TestCase struct { | ||||
| 		Counter uint64 | ||||
| 		Dup     bool | ||||
| 	} | ||||
|  | ||||
| 	testCases := []TestCase{ | ||||
| 		{0, true}, | ||||
| 		{1, true}, | ||||
| 		{2, true}, | ||||
| 		{3, true}, | ||||
| 		{63, true}, | ||||
| 		{256, true}, | ||||
| 		{510, true}, | ||||
| 		{511, true}, | ||||
| 		{512, false}, | ||||
| 		{0, true}, | ||||
| 		{512, true}, | ||||
| 		{513, false}, | ||||
| 		{517, false}, | ||||
| 		{512, true}, | ||||
| 		{513, true}, | ||||
| 		{514, false}, | ||||
| 		{515, false}, | ||||
| 		{516, false}, | ||||
| 		{517, true}, | ||||
| 		{2512, false}, | ||||
| 		{2000, true}, | ||||
| 		{2001, false}, | ||||
| 		{4000, false}, | ||||
| 		{4000 - 512, true},  // Too old. | ||||
| 		{4000 - 511, false}, // Just in the window. | ||||
| 	} | ||||
|  | ||||
| 	for i, tc := range testCases { | ||||
| 		if ok := dc.IsDup(tc.Counter); ok != tc.Dup { | ||||
| 			t.Fatal(i, ok, tc) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,82 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"vppn/m" | ||||
| ) | ||||
|  | ||||
| func configDir(netName string) string { | ||||
| 	d, err := os.UserHomeDir() | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to get user home directory: %v", err) | ||||
| 	} | ||||
| 	return filepath.Join(d, ".vppn", netName) | ||||
| } | ||||
|  | ||||
| func peerConfigPath(netName string) string { | ||||
| 	return filepath.Join(configDir(netName), "peer-config.json") | ||||
| } | ||||
|  | ||||
| func peerStatePath(netName string) string { | ||||
| 	return filepath.Join(configDir(netName), "peer-state.json") | ||||
| } | ||||
|  | ||||
| func storeJson(x any, outPath string) error { | ||||
| 	outDir := filepath.Dir(outPath) | ||||
| 	_ = os.MkdirAll(outDir, 0700) | ||||
|  | ||||
| 	tmpPath := outPath + ".tmp" | ||||
| 	buf, err := json.Marshal(x) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	f, err := os.Create(tmpPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if _, err := f.Write(buf); err != nil { | ||||
| 		f.Close() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := f.Sync(); err != nil { | ||||
| 		f.Close() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := f.Close(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return os.Rename(tmpPath, outPath) | ||||
| } | ||||
|  | ||||
| func storePeerConfig(netName string, pc localConfig) error { | ||||
| 	return storeJson(pc, peerConfigPath(netName)) | ||||
| } | ||||
|  | ||||
| func storeNetworkState(netName string, ps m.NetworkState) error { | ||||
| 	return storeJson(ps, peerStatePath(netName)) | ||||
| } | ||||
|  | ||||
| func loadJson(dataPath string, ptr any) error { | ||||
| 	data, err := os.ReadFile(dataPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return json.Unmarshal(data, ptr) | ||||
| } | ||||
|  | ||||
| func loadPeerConfig(netName string) (pc localConfig, err error) { | ||||
| 	return pc, loadJson(peerConfigPath(netName), &pc) | ||||
| } | ||||
|  | ||||
| func loadNetworkState(netName string) (ps m.NetworkState, err error) { | ||||
| 	return ps, loadJson(peerStatePath(netName), &ps) | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| func getRelayRoute() *peerRoute { | ||||
| 	if ip := relayIP.Load(); ip != nil { | ||||
| 		return routingTable[*ip].Load() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"net/netip" | ||||
| 	"net/url" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	bufferSize            = 1536 | ||||
| 	if_mtu                = 1200 | ||||
| 	if_queue_len          = 2048 | ||||
| 	controlCipherOverhead = 16 | ||||
| 	dataCipherOverhead    = 16 | ||||
| 	signOverhead          = 64 | ||||
| ) | ||||
|  | ||||
| var multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom( | ||||
| 	netip.AddrFrom4([4]byte{224, 0, 0, 157}), | ||||
| 	4560)) | ||||
|  | ||||
| var ( | ||||
| 	hubURL *url.URL | ||||
| 	apiKey string | ||||
|  | ||||
| 	// Configuration for this peer. | ||||
| 	netName     string | ||||
| 	localIP     byte | ||||
| 	localPub    bool | ||||
| 	localAddr   netip.AddrPort | ||||
| 	privKey     []byte | ||||
| 	privSignKey []byte | ||||
|  | ||||
| 	// TODO: Doesn't need to be global. | ||||
| 	// Duplicate checkers for incoming packets. | ||||
| 	dupChecks [256]*dupCheck = func() (out [256]*dupCheck) { | ||||
| 		for i := range out { | ||||
| 			out[i] = newDupCheck(0) | ||||
| 		} | ||||
| 		return | ||||
| 	}() | ||||
|  | ||||
| 	// TODO: Doesn't need to be global . | ||||
| 	// Messages for the supervisor. | ||||
| 	messages = make(chan any, 1024) | ||||
|  | ||||
| 	// TODO: Doesn't need to be global . | ||||
| 	// Global routing table. | ||||
| 	routingTable [256]*atomic.Pointer[peerRoute] = func() (out [256]*atomic.Pointer[peerRoute]) { | ||||
| 		for i := range out { | ||||
| 			out[i] = &atomic.Pointer[peerRoute]{} | ||||
| 			out[i].Store(&peerRoute{}) | ||||
| 		} | ||||
| 		return | ||||
| 	}() | ||||
|  | ||||
| 	// Managed by the relayManager. | ||||
| 	relayIP = &atomic.Pointer[byte]{} | ||||
|  | ||||
| 	// TODO: Only used by supervisor: can make local there. | ||||
| 	publicAddrs = newPubAddrStore() | ||||
| ) | ||||
| @@ -1,49 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import "unsafe" | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| const ( | ||||
| 	headerSize        = 12 | ||||
| 	controlStreamID   = 2 | ||||
| 	controlHeaderSize = 24 | ||||
| 	dataStreamID      = 1 | ||||
| 	dataHeaderSize    = 12 | ||||
| ) | ||||
|  | ||||
| type header struct { | ||||
| 	Version  byte | ||||
| 	StreamID byte | ||||
| 	SourceIP byte | ||||
| 	DestIP   byte | ||||
| 	Counter  uint64 // Init with time.Now().Unix << 30 to ensure monotonic. | ||||
| } | ||||
|  | ||||
| func parseHeader(b []byte) (h header, ok bool) { | ||||
| 	if len(b) < headerSize { | ||||
| 		return | ||||
| 	} | ||||
| 	h.Version = b[0] | ||||
| 	h.StreamID = b[1] | ||||
| 	h.SourceIP = b[2] | ||||
| 	h.DestIP = b[3] | ||||
| 	h.Counter = *(*uint64)(unsafe.Pointer(&b[4])) | ||||
| 	return h, true | ||||
| } | ||||
|  | ||||
| func (h *header) Parse(b []byte) { | ||||
| 	h.Version = b[0] | ||||
| 	h.StreamID = b[1] | ||||
| 	h.SourceIP = b[2] | ||||
| 	h.DestIP = b[3] | ||||
| 	h.Counter = *(*uint64)(unsafe.Pointer(&b[4])) | ||||
| } | ||||
|  | ||||
| func (h *header) Marshal(buf []byte) { | ||||
| 	buf[0] = h.Version | ||||
| 	buf[1] = h.StreamID | ||||
| 	buf[2] = h.SourceIP | ||||
| 	buf[3] = h.DestIP | ||||
| 	*(*uint64)(unsafe.Pointer(&buf[4])) = h.Counter | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import "testing" | ||||
|  | ||||
| func TestHeaderMarshalParse(t *testing.T) { | ||||
| 	nIn := header{ | ||||
| 		StreamID: 23, | ||||
| 		Counter:  3212, | ||||
| 		SourceIP: 34, | ||||
| 		DestIP:   200, | ||||
| 	} | ||||
|  | ||||
| 	buf := make([]byte, headerSize) | ||||
| 	nIn.Marshal(buf) | ||||
|  | ||||
| 	nOut := header{} | ||||
| 	nOut.Parse(buf) | ||||
| 	if nIn != nOut { | ||||
| 		t.Fatal(nIn, nOut) | ||||
| 	} | ||||
| } | ||||
| @@ -1,92 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| 	"vppn/m" | ||||
| ) | ||||
|  | ||||
| type hubPoller struct { | ||||
| 	client   *http.Client | ||||
| 	req      *http.Request | ||||
| 	versions [256]int64 | ||||
| } | ||||
|  | ||||
| func newHubPoller() *hubPoller { | ||||
| 	u := *hubURL | ||||
| 	u.Path = "/peer/fetch-state/" | ||||
|  | ||||
| 	client := &http.Client{Timeout: 8 * time.Second} | ||||
|  | ||||
| 	req := &http.Request{ | ||||
| 		Method: http.MethodGet, | ||||
| 		URL:    &u, | ||||
| 		Header: http.Header{}, | ||||
| 	} | ||||
| 	req.SetBasicAuth("", apiKey) | ||||
|  | ||||
| 	return &hubPoller{ | ||||
| 		client: client, | ||||
| 		req:    req, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (hp *hubPoller) Run() { | ||||
| 	defer panicHandler() | ||||
|  | ||||
| 	state, err := loadNetworkState(netName) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to load network state: %v", err) | ||||
| 		log.Printf("Polling hub...") | ||||
| 		hp.pollHub() | ||||
| 	} else { | ||||
| 		hp.applyNetworkState(state) | ||||
| 	} | ||||
|  | ||||
| 	for range time.Tick(64 * time.Second) { | ||||
| 		hp.pollHub() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (hp *hubPoller) pollHub() { | ||||
| 	var state m.NetworkState | ||||
|  | ||||
| 	resp, err := hp.client.Do(hp.req) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to fetch peer state: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	body, err := io.ReadAll(resp.Body) | ||||
| 	_ = resp.Body.Close() | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to read body from hub: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := json.Unmarshal(body, &state); err != nil { | ||||
| 		log.Printf("Failed to unmarshal response from hub: %v\n%s", err, body) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	hp.applyNetworkState(state) | ||||
|  | ||||
| 	if err := storeNetworkState(netName, state); err != nil { | ||||
| 		log.Printf("Failed to store network state: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (hp *hubPoller) applyNetworkState(state m.NetworkState) { | ||||
| 	for i, peer := range state.Peers { | ||||
| 		if i != int(localIP) { | ||||
| 			if peer == nil || peer.Version != hp.versions[i] { | ||||
| 				messages <- peerUpdateMsg{PeerIP: byte(i), Peer: state.Peers[i]} | ||||
| 				if peer != nil { | ||||
| 					hp.versions[i] = peer.Version | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										102
									
								
								node/ifreader.go
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								node/ifreader.go
									
									
									
									
									
								
							| @@ -1,102 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
|  | ||||
| type ifReader struct { | ||||
| 	iface           io.Reader | ||||
| 	routes          [256]*atomic.Pointer[peerRoute] | ||||
| 	relay           *atomic.Pointer[peerRoute] | ||||
| 	sendDataPacket  func(pkt []byte, route *peerRoute) | ||||
| 	relayDataPacket func(pkt []byte, route, relay *peerRoute) | ||||
| } | ||||
|  | ||||
| func newIFReader( | ||||
| 	iface io.Reader, | ||||
| 	routes [256]*atomic.Pointer[peerRoute], | ||||
| 	relay *atomic.Pointer[peerRoute], | ||||
| 	sendDataPacket func(pkt []byte, route *peerRoute), | ||||
| 	relayDackPacket func(pkt []byte, route, relay *peerRoute), | ||||
| ) *ifReader { | ||||
| 	return &ifReader{ | ||||
| 		iface:          iface, | ||||
| 		routes:         routes, | ||||
| 		relay:          relay, | ||||
| 		sendDataPacket: sendDataPacket, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *ifReader) Run() { | ||||
| 	var ( | ||||
| 		packet   = make([]byte, bufferSize) | ||||
| 		remoteIP byte | ||||
| 		ok       bool | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		packet = r.readNextPacket(packet) | ||||
| 		if remoteIP, ok = r.parsePacket(packet); ok { | ||||
| 			r.sendPacket(packet, remoteIP) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *ifReader) sendPacket(pkt []byte, remoteIP byte) { | ||||
| 	route := r.routes[remoteIP].Load() | ||||
| 	if !route.Up { | ||||
| 		log.Printf("Route not connected: %d", remoteIP) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Direct path => early return. | ||||
| 	if route.Direct { | ||||
| 		r.sendDataPacket(pkt, route) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if relay := r.relay.Load(); relay != nil && relay.Up { | ||||
| 		r.relayDataPacket(pkt, route, relay) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Get next packet, returning packet, and destination ip. | ||||
| func (r *ifReader) readNextPacket(buf []byte) []byte { | ||||
| 	n, err := r.iface.Read(buf[:cap(buf)]) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to read from interface: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return buf[:n] | ||||
| } | ||||
|  | ||||
| func (r *ifReader) parsePacket(buf []byte) (byte, bool) { | ||||
| 	n := len(buf) | ||||
| 	if n == 0 { | ||||
| 		return 0, false | ||||
| 	} | ||||
|  | ||||
| 	version := buf[0] >> 4 | ||||
|  | ||||
| 	switch version { | ||||
| 	case 4: | ||||
| 		if n < 20 { | ||||
| 			log.Printf("Short IPv4 packet: %d", len(buf)) | ||||
| 			return 0, false | ||||
| 		} | ||||
| 		return buf[19], true | ||||
|  | ||||
| 	case 6: | ||||
| 		if len(buf) < 40 { | ||||
| 			log.Printf("Short IPv6 packet: %d", len(buf)) | ||||
| 			return 0, false | ||||
| 		} | ||||
| 		return buf[39], true | ||||
|  | ||||
| 	default: | ||||
| 		log.Printf("Invalid IP packet version: %v", version) | ||||
| 		return 0, false | ||||
| 	} | ||||
| } | ||||
| @@ -1,117 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"net" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // Test that we parse IPv4 packets correctly. | ||||
| func TestIFReader_parsePacket_ipv4(t *testing.T) { | ||||
| 	r := newIFReader(nil, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
|  | ||||
| 	pkt := make([]byte, 1234) | ||||
| 	pkt[0] = 4 << 4 | ||||
| 	pkt[19] = 128 | ||||
|  | ||||
| 	if ip, ok := r.parsePacket(pkt); !ok || ip != 128 { | ||||
| 		t.Fatal(ip, ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test that we parse IPv6 packets correctly. | ||||
| func TestIFReader_parsePacket_ipv6(t *testing.T) { | ||||
| 	r := newIFReader(nil, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
|  | ||||
| 	pkt := make([]byte, 1234) | ||||
| 	pkt[0] = 6 << 4 | ||||
| 	pkt[39] = 42 | ||||
|  | ||||
| 	if ip, ok := r.parsePacket(pkt); !ok || ip != 42 { | ||||
| 		t.Fatal(ip, ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test that empty packets work as expected. | ||||
| func TestIFReader_parsePacket_emptyPacket(t *testing.T) { | ||||
| 	r := newIFReader(nil, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
|  | ||||
| 	pkt := make([]byte, 0) | ||||
| 	if ip, ok := r.parsePacket(pkt); ok { | ||||
| 		t.Fatal(ip, ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test that invalid IP versions fail. | ||||
| func TestIFReader_parsePacket_invalidIPVersion(t *testing.T) { | ||||
| 	r := newIFReader(nil, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
|  | ||||
| 	for i := byte(1); i < 16; i++ { | ||||
| 		if i == 4 || i == 6 { | ||||
| 			continue | ||||
| 		} | ||||
| 		pkt := make([]byte, 1234) | ||||
| 		pkt[0] = i << 4 | ||||
|  | ||||
| 		if ip, ok := r.parsePacket(pkt); ok { | ||||
| 			t.Fatal(i, ip, ok) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test that short IPv4 packets fail. | ||||
| func TestIFReader_parsePacket_shortIPv4(t *testing.T) { | ||||
| 	r := newIFReader(nil, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
|  | ||||
| 	pkt := make([]byte, 19) | ||||
| 	pkt[0] = 4 << 4 | ||||
|  | ||||
| 	if ip, ok := r.parsePacket(pkt); ok { | ||||
| 		t.Fatal(ip, ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test that short IPv6 packets fail. | ||||
| func TestIFReader_parsePacket_shortIPv6(t *testing.T) { | ||||
| 	r := newIFReader(nil, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
|  | ||||
| 	pkt := make([]byte, 39) | ||||
| 	pkt[0] = 6 << 4 | ||||
|  | ||||
| 	if ip, ok := r.parsePacket(pkt); ok { | ||||
| 		t.Fatal(ip, ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test that we can read a packet. | ||||
| func TestIFReader_readNextpacket(t *testing.T) { | ||||
| 	in, out := net.Pipe() | ||||
| 	r := newIFReader(out, [256]*atomic.Pointer[peerRoute]{}, nil, nil, nil) | ||||
| 	defer in.Close() | ||||
| 	defer out.Close() | ||||
|  | ||||
| 	go in.Write([]byte("hello world!")) | ||||
|  | ||||
| 	pkt := r.readNextPacket(make([]byte, bufferSize)) | ||||
| 	if !bytes.Equal(pkt, []byte("hello world!")) { | ||||
| 		t.Fatalf("%s", pkt) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing that we can send a packet directly. | ||||
| func TestIFReader_sendPacket_direct(t *testing.T) { | ||||
| 	// TODO | ||||
| } | ||||
|  | ||||
| // Testing that we don't send a packet if route isn't up. | ||||
| func TestIFReader_sendPacket_directNotUp(t *testing.T) { | ||||
| 	// TODO | ||||
| } | ||||
|  | ||||
| // Testing that we can send a packet via a relay. | ||||
| func TestIFReader_sendPacket_relayed(t *testing.T) { | ||||
| 	// TODO | ||||
| } | ||||
|  | ||||
| // Testing that we don't try to send on a nil relay IP. | ||||
| @@ -1,5 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import "io" | ||||
|  | ||||
| type ifWriter io.Writer | ||||
| @@ -1,177 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| // Get next packet, returning packet, ip, and possible error. | ||||
| func readNextPacket(iface io.ReadWriteCloser, buf []byte) ([]byte, byte, error) { | ||||
| 	var ( | ||||
| 		version byte | ||||
| 		ip      byte | ||||
| 	) | ||||
| 	for { | ||||
| 		n, err := iface.Read(buf[:cap(buf)]) | ||||
| 		if err != nil { | ||||
| 			return nil, ip, err | ||||
| 		} | ||||
|  | ||||
| 		buf = buf[:n] | ||||
| 		version = buf[0] >> 4 | ||||
|  | ||||
| 		switch version { | ||||
| 		case 4: | ||||
| 			if n < 20 { | ||||
| 				log.Printf("Short IPv4 packet: %d", len(buf)) | ||||
| 				continue | ||||
| 			} | ||||
| 			ip = buf[19] | ||||
|  | ||||
| 		case 6: | ||||
| 			if len(buf) < 40 { | ||||
| 				log.Printf("Short IPv6 packet: %d", len(buf)) | ||||
| 				continue | ||||
| 			} | ||||
| 			ip = buf[39] | ||||
|  | ||||
| 		default: | ||||
| 			log.Printf("Invalid IP packet version: %v", version) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		return buf, ip, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func openInterface(network []byte, localIP byte, name string) (io.ReadWriteCloser, error) { | ||||
| 	if len(network) != 4 { | ||||
| 		return nil, fmt.Errorf("expected network to be 4 bytes, got %d", len(network)) | ||||
| 	} | ||||
| 	ip := net.IPv4(network[0], network[1], network[2], localIP) | ||||
|  | ||||
| 	////////////////////////// | ||||
| 	// Create TUN Interface // | ||||
| 	////////////////////////// | ||||
|  | ||||
| 	tunFD, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|unix.O_CLOEXEC, 0600) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to open TUN device: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// New interface request. | ||||
| 	req, err := unix.NewIfreq(name) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create new TUN interface request: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Flags: | ||||
| 	// | ||||
| 	// IFF_NO_PI => don't add packet info data to packets sent to the interface. | ||||
| 	// IFF_TUN   => create a TUN device handling IP packets. | ||||
| 	req.SetUint16(unix.IFF_NO_PI | unix.IFF_TUN) | ||||
|  | ||||
| 	err = unix.IoctlIfreq(tunFD, unix.TUNSETIFF, req) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set TUN device settings: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Name may not be exactly the same? | ||||
| 	name = req.Name() | ||||
|  | ||||
| 	///////////// | ||||
| 	// Set MTU // | ||||
| 	///////////// | ||||
|  | ||||
| 	// We need a socket file descriptor to set other options for some reason. | ||||
| 	sockFD, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to open socket: %w", err) | ||||
| 	} | ||||
| 	defer unix.Close(sockFD) | ||||
|  | ||||
| 	req, err = unix.NewIfreq(name) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create MTU interface request: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	req.SetUint32(if_mtu) | ||||
| 	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFMTU, req); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface MTU: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	////////////////////// | ||||
| 	// Set Queue Length // | ||||
| 	////////////////////// | ||||
|  | ||||
| 	req, err = unix.NewIfreq(name) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create IP interface request: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	req.SetUint16(if_queue_len) | ||||
| 	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFTXQLEN, req); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface queue length: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	///////////////////// | ||||
| 	// Set IP and Mask // | ||||
| 	///////////////////// | ||||
|  | ||||
| 	req, err = unix.NewIfreq(name) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create IP interface request: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := req.SetInet4Addr(ip.To4()); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface request IP: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFADDR, req); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface IP: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// SET MASK - must happen after setting address. | ||||
| 	req, err = unix.NewIfreq(name) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create mask interface request: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := req.SetInet4Addr(net.IPv4(255, 255, 255, 0).To4()); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface request mask: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := unix.IoctlIfreq(sockFD, unix.SIOCSIFNETMASK, req); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface mask: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	//////////////////////// | ||||
| 	// Bring Interface Up // | ||||
| 	//////////////////////// | ||||
|  | ||||
| 	req, err = unix.NewIfreq(name) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create up interface request: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get current flags. | ||||
| 	if err = unix.IoctlIfreq(sockFD, unix.SIOCGIFFLAGS, req); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to get interface flags: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	flags := req.Uint16() | unix.IFF_UP | unix.IFF_RUNNING | ||||
|  | ||||
| 	// Set UP flag / broadcast flags. | ||||
| 	req.SetUint16(flags) | ||||
| 	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFFLAGS, req); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to set interface up: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return os.NewFile(uintptr(tunFD), "tun"), nil | ||||
| } | ||||
| @@ -1,97 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/sign" | ||||
| ) | ||||
|  | ||||
| func localDiscovery() { | ||||
| 	conn, err := net.ListenMulticastUDP("udp", nil, multicastAddr) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to bind to multicast address: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	go sendLocalDiscovery(conn) | ||||
| 	go recvLocalDiscovery(conn) | ||||
| } | ||||
|  | ||||
| func sendLocalDiscovery(conn *net.UDPConn) { | ||||
| 	var ( | ||||
| 		buf1 = make([]byte, bufferSize) | ||||
| 		buf2 = make([]byte, bufferSize) | ||||
| 	) | ||||
|  | ||||
| 	for range time.Tick(16 * time.Second) { | ||||
| 		signed := buildLocalDiscoveryPacket(buf1, buf2) | ||||
| 		if _, err := conn.WriteToUDP(signed, multicastAddr); err != nil { | ||||
| 			log.Printf("Failed to write multicast UDP packet: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func recvLocalDiscovery(conn *net.UDPConn) { | ||||
| 	var ( | ||||
| 		raw = make([]byte, bufferSize) | ||||
| 		buf = make([]byte, bufferSize) | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		n, remoteAddr, err := conn.ReadFromUDPAddrPort(raw[:bufferSize]) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Failed to read from UDP port (multicast): %v", err) | ||||
| 		} | ||||
|  | ||||
| 		raw = raw[:n] | ||||
| 		h, ok := openLocalDiscoveryPacket(raw, buf) | ||||
| 		if !ok { | ||||
| 			log.Printf("Failed to open discovery packet?") | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		msg := controlMsg[localDiscoveryPacket]{ | ||||
| 			SrcIP:   h.SourceIP, | ||||
| 			SrcAddr: remoteAddr, | ||||
| 			Packet:  localDiscoveryPacket{}, | ||||
| 		} | ||||
|  | ||||
| 		select { | ||||
| 		case messages <- msg: | ||||
| 		default: | ||||
| 			log.Printf("Dropping local discovery message.") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func buildLocalDiscoveryPacket(buf1, buf2 []byte) []byte { | ||||
| 	h := header{ | ||||
| 		StreamID: controlStreamID, | ||||
| 		Counter:  0, | ||||
| 		SourceIP: localIP, | ||||
| 		DestIP:   255, | ||||
| 	} | ||||
| 	out := buf1[:headerSize] | ||||
| 	h.Marshal(out) | ||||
| 	return sign.Sign(buf2[:0], out, (*[64]byte)(privSignKey)) | ||||
| } | ||||
|  | ||||
| func openLocalDiscoveryPacket(raw, buf []byte) (h header, ok bool) { | ||||
| 	if len(raw) != headerSize+signOverhead { | ||||
| 		ok = false | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	h.Parse(raw[signOverhead:]) | ||||
| 	route := routingTable[h.SourceIP].Load() | ||||
| 	if route == nil || route.PubSignKey == nil { | ||||
| 		log.Printf("Missing signing key: %d", h.SourceIP) | ||||
| 		ok = false | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	_, ok = sign.Open(buf[:0], raw, (*[32]byte)(route.PubSignKey)) | ||||
| 	return | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"testing" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/sign" | ||||
| ) | ||||
|  | ||||
| func TestLocalDiscoveryPacketSigning(t *testing.T) { | ||||
| 	localIP = 32 | ||||
|  | ||||
| 	var ( | ||||
| 		buf1                      = make([]byte, bufferSize) | ||||
| 		buf2                      = make([]byte, bufferSize) | ||||
| 		pubSignKey, privSigKey, _ = sign.GenerateKey(rand.Reader) | ||||
| 	) | ||||
|  | ||||
| 	privSignKey = privSigKey[:] | ||||
| 	route := routingTable[localIP].Load() | ||||
| 	route.IP = byte(localIP) | ||||
| 	route.PubSignKey = pubSignKey[:] | ||||
| 	routingTable[localIP].Store(route) | ||||
|  | ||||
| 	out := buildLocalDiscoveryPacket(buf1, buf2) | ||||
|  | ||||
| 	h, ok := openLocalDiscoveryPacket(bytes.Clone(out), buf1) | ||||
| 	if !ok { | ||||
| 		t.Fatal(h, ok) | ||||
| 	} | ||||
| 	if h.StreamID != controlStreamID || h.SourceIP != localIP || h.DestIP != 255 { | ||||
| 		t.Fatal(h) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										320
									
								
								node/main.go
									
									
									
									
									
								
							
							
						
						
									
										320
									
								
								node/main.go
									
									
									
									
									
								
							| @@ -1,320 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/netip" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"runtime/debug" | ||||
| 	"time" | ||||
| 	"vppn/m" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/box" | ||||
| 	"golang.org/x/crypto/nacl/sign" | ||||
| ) | ||||
|  | ||||
| func panicHandler() { | ||||
| 	if r := recover(); r != nil { | ||||
| 		log.Fatalf("\n %v\n\nstacktrace from panic: %s\n", r, string(debug.Stack())) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Main() { | ||||
| 	defer panicHandler() | ||||
|  | ||||
| 	var hubAddress string | ||||
|  | ||||
| 	flag.StringVar(&netName, "name", "", "[REQUIRED] The network name.") | ||||
| 	flag.StringVar(&hubAddress, "hub-address", "", "[REQUIRED] The hub address.") | ||||
| 	flag.StringVar(&apiKey, "api-key", "", "[REQUIRED] The node's API key.") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	if netName == "" || hubAddress == "" || apiKey == "" { | ||||
| 		flag.Usage() | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
|  | ||||
| 	hubURL, err = url.Parse(hubAddress) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to parse hub address: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	main() | ||||
| } | ||||
|  | ||||
| func initPeerWithHub() { | ||||
| 	encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to generate encryption keys: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to generate signing keys: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	initURL := *hubURL | ||||
| 	initURL.Path = "/peer/init/" | ||||
|  | ||||
| 	args := m.PeerInitArgs{ | ||||
| 		EncPubKey:  encPubKey[:], | ||||
| 		PubSignKey: signPubKey[:], | ||||
| 	} | ||||
|  | ||||
| 	buf := &bytes.Buffer{} | ||||
| 	if err := json.NewEncoder(buf).Encode(args); err != nil { | ||||
| 		log.Fatalf("Failed to encode init args: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest(http.MethodPost, initURL.String(), buf) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to construct request: %v", err) | ||||
| 	} | ||||
| 	req.SetBasicAuth("", apiKey) | ||||
|  | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to init with hub: %v", err) | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	data, err := io.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to read response body: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	peerConfig := localConfig{} | ||||
| 	if err := json.Unmarshal(data, &peerConfig.PeerConfig); err != nil { | ||||
| 		log.Fatalf("Failed to parse configuration: %v\n%s", err, data) | ||||
| 	} | ||||
|  | ||||
| 	peerConfig.PubKey = encPubKey[:] | ||||
| 	peerConfig.PrivKey = encPrivKey[:] | ||||
| 	peerConfig.PubSignKey = signPubKey[:] | ||||
| 	peerConfig.PrivSignKey = signPrivKey[:] | ||||
|  | ||||
| 	if err := storePeerConfig(netName, peerConfig); err != nil { | ||||
| 		log.Fatalf("Failed to store configuration: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	log.Print("Initialization successful.") | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func main() { | ||||
| 	config, err := loadPeerConfig(netName) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to load configuration: %v", err) | ||||
| 		log.Printf("Initializing...") | ||||
| 		initPeerWithHub() | ||||
|  | ||||
| 		config, err = loadPeerConfig(netName) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Failed to load configuration: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	iface, err := openInterface(config.Network, config.PeerIP, netName) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to open interface: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", config.Port)) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to resolve UDP address: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	conn, err := net.ListenUDP("udp", myAddr) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to open UDP port: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	conn.SetReadBuffer(1024 * 1024 * 8) | ||||
| 	conn.SetWriteBuffer(1024 * 1024 * 8) | ||||
|  | ||||
| 	localIP = config.PeerIP | ||||
|  | ||||
| 	ip, ok := netip.AddrFromSlice(config.PublicIP) | ||||
| 	if ok { | ||||
| 		localPub = true | ||||
| 		localAddr = netip.AddrPortFrom(ip, config.Port) | ||||
| 	} | ||||
|  | ||||
| 	privKey = config.PrivKey | ||||
| 	privSignKey = config.PrivSignKey | ||||
|  | ||||
| 	if !localPub { | ||||
| 		go relayManager() | ||||
| 		go localDiscovery() | ||||
| 	} | ||||
|  | ||||
| 	go func() { | ||||
| 		for range time.Tick(pingInterval) { | ||||
| 			messages <- pingTimerMsg{} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	sender := newPacketSender(conn) | ||||
|  | ||||
| 	go startPeerSuper(routingTable, messages, sender) | ||||
|  | ||||
| 	go newHubPoller().Run() | ||||
| 	go readFromConn(conn, iface, sender) | ||||
|  | ||||
| 	readFromIFace(iface, sender) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func readFromConn(conn *net.UDPConn, iface io.ReadWriteCloser, sender dataPacketSender) { | ||||
|  | ||||
| 	defer panicHandler() | ||||
|  | ||||
| 	var ( | ||||
| 		remoteAddr netip.AddrPort | ||||
| 		n          int | ||||
| 		err        error | ||||
| 		buf        = make([]byte, bufferSize) | ||||
| 		decBuf     = make([]byte, bufferSize) | ||||
| 		data       []byte | ||||
| 		h          header | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		n, remoteAddr, err = conn.ReadFromUDPAddrPort(buf[:bufferSize]) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Failed to read from UDP port: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		remoteAddr = netip.AddrPortFrom(remoteAddr.Addr().Unmap(), remoteAddr.Port()) | ||||
|  | ||||
| 		data = buf[:n] | ||||
|  | ||||
| 		if n < headerSize { | ||||
| 			continue // Packet it soo short. | ||||
| 		} | ||||
|  | ||||
| 		h.Parse(data) | ||||
| 		switch h.StreamID { | ||||
| 		case controlStreamID: | ||||
| 			handleControlPacket(remoteAddr, h, data, decBuf) | ||||
|  | ||||
| 		case dataStreamID: | ||||
| 			handleDataPacket(h, data, decBuf, iface, sender) | ||||
|  | ||||
| 		default: | ||||
| 			log.Printf("Unknown stream ID: %d", h.StreamID) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) { | ||||
| 	route := routingTable[h.SourceIP].Load() | ||||
| 	if route.ControlCipher == nil { | ||||
| 		//log.Printf("Not connected (control).") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if h.DestIP != localIP { | ||||
| 		log.Printf("Incorrect destination IP on control packet: %#v", h) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	out, ok := route.ControlCipher.Decrypt(data, decBuf) | ||||
| 	if !ok { | ||||
| 		log.Printf("Failed to decrypt control packet.") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(out) == 0 { | ||||
| 		log.Printf("Empty control packet from: %d", h.SourceIP) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if dupChecks[h.SourceIP].IsDup(h.Counter) { | ||||
| 		log.Printf("[%03d] Duplicate control packet: %d", h.SourceIP, h.Counter) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	msg, err := parseControlMsg(h.SourceIP, addr, out) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to parse control packet: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	select { | ||||
| 	case messages <- msg: | ||||
| 	default: | ||||
| 		log.Printf("Dropping control packet.") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handleDataPacket(h header, data []byte, decBuf []byte, iface ifWriter, sender dataPacketSender) { | ||||
| 	route := routingTable[h.SourceIP].Load() | ||||
| 	if !route.Up { | ||||
| 		log.Printf("Not connected (recv).") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	dec, ok := route.DataCipher.Decrypt(data, decBuf) | ||||
| 	if !ok { | ||||
| 		log.Printf("Failed to decrypt data packet.") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if dupChecks[h.SourceIP].IsDup(h.Counter) { | ||||
| 		log.Printf("[%03d] Duplicate data packet: %d", h.SourceIP, h.Counter) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if h.DestIP == localIP { | ||||
| 		if _, err := iface.Write(dec); err != nil { | ||||
| 			log.Fatalf("Failed to write to interface: %v", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	destRoute := routingTable[h.DestIP].Load() | ||||
| 	if !destRoute.Up { | ||||
| 		log.Printf("Not connected (relay): %d", destRoute.IP) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	sender.SendEncryptedDataPacket(dec, destRoute.RemoteAddr) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func readFromIFace(iface io.ReadWriteCloser, sender dataPacketSender) { | ||||
| 	var ( | ||||
| 		packet   = make([]byte, bufferSize) | ||||
| 		remoteIP byte | ||||
| 		err      error | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		packet, remoteIP, err = readNextPacket(iface, packet) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Failed to read from interface: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		route := routingTable[remoteIP].Load() | ||||
| 		if !route.Up { | ||||
| 			log.Printf("Route not connected: %d", remoteIP) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		sender.SendDataPacket(packet, *route) | ||||
| 	} | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"log" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/box" | ||||
| 	"golang.org/x/crypto/nacl/sign" | ||||
| ) | ||||
|  | ||||
| type testPeer struct { | ||||
| 	IP          byte | ||||
| 	PubKey      []byte | ||||
| 	PrivKey     []byte | ||||
| 	PubSignKey  []byte | ||||
| 	PrivSignKey []byte | ||||
| } | ||||
|  | ||||
| func newTestPeer(ip byte) testPeer { | ||||
| 	encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to generate encryption keys: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to generate signing keys: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return testPeer{ | ||||
| 		IP:          ip, | ||||
| 		PubKey:      encPubKey[:], | ||||
| 		PrivKey:     encPrivKey[:], | ||||
| 		PubSignKey:  signPubKey[:], | ||||
| 		PrivSignKey: signPrivKey[:], | ||||
| 	} | ||||
| } | ||||
| @@ -1,62 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
|  | ||||
| 	"golang.org/x/crypto/nacl/sign" | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type udpWriter interface { | ||||
| 	WriteToUDP([]byte, *net.UDPAddr) (int, error) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func createLocalDiscoveryPacket(localIP byte, signingKey []byte) []byte { | ||||
| 	h := header{ | ||||
| 		SourceIP: localIP, | ||||
| 		DestIP:   255, | ||||
| 	} | ||||
| 	buf := make([]byte, headerSize) | ||||
| 	h.Marshal(buf) | ||||
| 	out := make([]byte, headerSize+signOverhead) | ||||
| 	return sign.Sign(out[:0], buf, (*[64]byte)(signingKey)) | ||||
| } | ||||
|  | ||||
| func headerFromLocalDiscoveryPacket(pkt []byte) (h header, ok bool) { | ||||
| 	if len(pkt) != headerSize+signOverhead { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	h.Parse(pkt[signOverhead:]) | ||||
| 	ok = true | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func verifyLocalDiscoveryPacket(pkt, buf []byte, pubSignKey []byte) bool { | ||||
| 	_, ok := sign.Open(buf[:0], pkt, (*[32]byte)(pubSignKey)) | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type mcWriter struct { | ||||
| 	conn            udpWriter | ||||
| 	discoveryPacket []byte | ||||
| } | ||||
|  | ||||
| func newMCWriter(conn udpWriter, localIP byte, signingKey []byte) *mcWriter { | ||||
| 	return &mcWriter{ | ||||
| 		conn:            conn, | ||||
| 		discoveryPacket: createLocalDiscoveryPacket(localIP, signingKey), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (w *mcWriter) SendLocalDiscovery() { | ||||
| 	if _, err := w.conn.WriteToUDP(w.discoveryPacket, multicastAddr); err != nil { | ||||
| 		log.Printf("Failed to write multicast UDP packet: %v", err) | ||||
| 	} | ||||
| } | ||||
| @@ -1,102 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"net" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // Testing that we can create and verify a local discovery packet. | ||||
| func TestVerifyLocalDiscoveryPacket_valid(t *testing.T) { | ||||
| 	keys := generateKeys() | ||||
|  | ||||
| 	created := createLocalDiscoveryPacket(55, keys.PrivSignKey) | ||||
|  | ||||
| 	header, ok := headerFromLocalDiscoveryPacket(created) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
| 	if header.SourceIP != 55 || header.DestIP != 255 { | ||||
| 		t.Fatal(header) | ||||
| 	} | ||||
|  | ||||
| 	if !verifyLocalDiscoveryPacket(created, make([]byte, 1024), keys.PubSignKey) { | ||||
| 		t.Fatal("Not valid") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing that we don't try to parse short packets. | ||||
| func TestVerifyLocalDiscoveryPacket_tooShort(t *testing.T) { | ||||
| 	keys := generateKeys() | ||||
|  | ||||
| 	created := createLocalDiscoveryPacket(55, keys.PrivSignKey) | ||||
|  | ||||
| 	_, ok := headerFromLocalDiscoveryPacket(created[:len(created)-1]) | ||||
| 	if ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Testing that modifying a packet makes it invalid. | ||||
| func TestVerifyLocalDiscoveryPacket_invalid(t *testing.T) { | ||||
| 	keys := generateKeys() | ||||
|  | ||||
| 	created := createLocalDiscoveryPacket(55, keys.PrivSignKey) | ||||
| 	buf := make([]byte, 1024) | ||||
| 	for i := range created { | ||||
| 		modified := bytes.Clone(created) | ||||
| 		modified[i]++ | ||||
| 		if verifyLocalDiscoveryPacket(modified, buf, keys.PubSignKey) { | ||||
| 			t.Fatal("Verification should have failed.") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type testUDPWriter struct { | ||||
| 	written [][]byte | ||||
| } | ||||
|  | ||||
| func (w *testUDPWriter) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { | ||||
| 	w.written = append(w.written, bytes.Clone(b)) | ||||
| 	return len(b), nil | ||||
| } | ||||
|  | ||||
| func (w *testUDPWriter) Written() [][]byte { | ||||
| 	out := w.written | ||||
| 	w.written = [][]byte{} | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // Testing that the mcWriter sends local discovery packets as expected. | ||||
| func TestMCWriter_SendLocalDiscovery(t *testing.T) { | ||||
| 	keys := generateKeys() | ||||
| 	writer := &testUDPWriter{} | ||||
|  | ||||
| 	mcw := newMCWriter(writer, 42, keys.PrivSignKey) | ||||
| 	mcw.SendLocalDiscovery() | ||||
|  | ||||
| 	out := writer.Written() | ||||
| 	if len(out) != 1 { | ||||
| 		t.Fatal(out) | ||||
| 	} | ||||
|  | ||||
| 	pkt := out[0] | ||||
|  | ||||
| 	header, ok := headerFromLocalDiscoveryPacket(pkt) | ||||
| 	if !ok { | ||||
| 		t.Fatal(ok) | ||||
| 	} | ||||
| 	if header.SourceIP != 42 || header.DestIP != 255 { | ||||
| 		t.Fatal(header) | ||||
| 	} | ||||
|  | ||||
| 	if !verifyLocalDiscoveryPacket(pkt, make([]byte, 1024), keys.PubSignKey) { | ||||
| 		t.Fatal("Verification should succeed.") | ||||
| 	} | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"net/netip" | ||||
| 	"vppn/m" | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type controlMsg[T any] struct { | ||||
| 	SrcIP   byte | ||||
| 	SrcAddr netip.AddrPort | ||||
| 	// TODO: RecvdAt int64 // Unixmilli. | ||||
| 	Packet T | ||||
| } | ||||
|  | ||||
| func parseControlMsg(srcIP byte, srcAddr netip.AddrPort, buf []byte) (any, error) { | ||||
| 	switch buf[0] { | ||||
|  | ||||
| 	case packetTypeSyn: | ||||
| 		packet, err := parseSynPacket(buf) | ||||
| 		return controlMsg[synPacket]{ | ||||
| 			SrcIP:   srcIP, | ||||
| 			SrcAddr: srcAddr, | ||||
| 			Packet:  packet, | ||||
| 		}, err | ||||
|  | ||||
| 	case packetTypeAck: | ||||
| 		packet, err := parseAckPacket(buf) | ||||
| 		return controlMsg[ackPacket]{ | ||||
| 			SrcIP:   srcIP, | ||||
| 			SrcAddr: srcAddr, | ||||
| 			Packet:  packet, | ||||
| 		}, err | ||||
|  | ||||
| 	case packetTypeProbe: | ||||
| 		packet, err := parseProbePacket(buf) | ||||
| 		return controlMsg[probePacket]{ | ||||
| 			SrcIP:   srcIP, | ||||
| 			SrcAddr: srcAddr, | ||||
| 			Packet:  packet, | ||||
| 		}, err | ||||
|  | ||||
| 	default: | ||||
| 		return nil, errUnknownPacketType | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type peerUpdateMsg struct { | ||||
| 	PeerIP byte | ||||
| 	Peer   *m.Peer | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type pingTimerMsg struct{} | ||||
| @@ -1,190 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"net/netip" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| var traceIDCounter uint64 = uint64(time.Now().Unix()<<30) + 1 | ||||
|  | ||||
| func newTraceID() uint64 { | ||||
| 	return atomic.AddUint64(&traceIDCounter, 1) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type binWriter struct { | ||||
| 	b []byte | ||||
| 	i int | ||||
| } | ||||
|  | ||||
| func newBinWriter(buf []byte) *binWriter { | ||||
| 	buf = buf[:cap(buf)] | ||||
| 	return &binWriter{buf, 0} | ||||
| } | ||||
|  | ||||
| func (w *binWriter) Bool(b bool) *binWriter { | ||||
| 	if b { | ||||
| 		return w.Byte(1) | ||||
| 	} | ||||
| 	return w.Byte(0) | ||||
| } | ||||
|  | ||||
| func (w *binWriter) Byte(b byte) *binWriter { | ||||
| 	w.b[w.i] = b | ||||
| 	w.i++ | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *binWriter) SharedKey(key [32]byte) *binWriter { | ||||
| 	copy(w.b[w.i:w.i+32], key[:]) | ||||
| 	w.i += 32 | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *binWriter) Uint16(x uint16) *binWriter { | ||||
| 	*(*uint16)(unsafe.Pointer(&w.b[w.i])) = x | ||||
| 	w.i += 2 | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *binWriter) Uint64(x uint64) *binWriter { | ||||
| 	*(*uint64)(unsafe.Pointer(&w.b[w.i])) = x | ||||
| 	w.i += 8 | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *binWriter) Int64(x int64) *binWriter { | ||||
| 	*(*int64)(unsafe.Pointer(&w.b[w.i])) = x | ||||
| 	w.i += 8 | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *binWriter) AddrPort(addrPort netip.AddrPort) *binWriter { | ||||
| 	w.Bool(addrPort.IsValid()) | ||||
| 	addr := addrPort.Addr().As16() | ||||
| 	copy(w.b[w.i:w.i+16], addr[:]) | ||||
| 	w.i += 16 | ||||
| 	return w.Uint16(addrPort.Port()) | ||||
| } | ||||
|  | ||||
| func (w *binWriter) AddrPortArray(l [8]netip.AddrPort) *binWriter { | ||||
| 	for _, addrPort := range l { | ||||
| 		w.AddrPort(addrPort) | ||||
| 	} | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func (w *binWriter) Build() []byte { | ||||
| 	return w.b[:w.i] | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type binReader struct { | ||||
| 	b   []byte | ||||
| 	i   int | ||||
| 	err error | ||||
| } | ||||
|  | ||||
| func newBinReader(buf []byte) *binReader { | ||||
| 	return &binReader{b: buf} | ||||
| } | ||||
|  | ||||
| func (r *binReader) hasBytes(n int) bool { | ||||
| 	if r.err != nil || (len(r.b)-r.i) < n { | ||||
| 		r.err = errMalformedPacket | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (r *binReader) Bool(b *bool) *binReader { | ||||
| 	var bb byte | ||||
| 	r.Byte(&bb) | ||||
| 	*b = bb != 0 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) Byte(b *byte) *binReader { | ||||
| 	if !r.hasBytes(1) { | ||||
| 		return r | ||||
| 	} | ||||
| 	*b = r.b[r.i] | ||||
| 	r.i++ | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) SharedKey(x *[32]byte) *binReader { | ||||
| 	if !r.hasBytes(32) { | ||||
| 		return r | ||||
| 	} | ||||
| 	*x = ([32]byte)(r.b[r.i : r.i+32]) | ||||
| 	r.i += 32 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) Uint16(x *uint16) *binReader { | ||||
| 	if !r.hasBytes(2) { | ||||
| 		return r | ||||
| 	} | ||||
| 	*x = *(*uint16)(unsafe.Pointer(&r.b[r.i])) | ||||
| 	r.i += 2 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) Uint64(x *uint64) *binReader { | ||||
| 	if !r.hasBytes(8) { | ||||
| 		return r | ||||
| 	} | ||||
| 	*x = *(*uint64)(unsafe.Pointer(&r.b[r.i])) | ||||
| 	r.i += 8 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) Int64(x *int64) *binReader { | ||||
| 	if !r.hasBytes(8) { | ||||
| 		return r | ||||
| 	} | ||||
| 	*x = *(*int64)(unsafe.Pointer(&r.b[r.i])) | ||||
| 	r.i += 8 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) AddrPort(x *netip.AddrPort) *binReader { | ||||
| 	if !r.hasBytes(19) { | ||||
| 		return r | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		valid bool | ||||
| 		port  uint16 | ||||
| 	) | ||||
|  | ||||
| 	r.Bool(&valid) | ||||
| 	addr := netip.AddrFrom16(([16]byte)(r.b[r.i : r.i+16])).Unmap() | ||||
| 	r.i += 16 | ||||
|  | ||||
| 	r.Uint16(&port) | ||||
|  | ||||
| 	if valid { | ||||
| 		*x = netip.AddrPortFrom(addr, port) | ||||
| 	} else { | ||||
| 		*x = netip.AddrPort{} | ||||
| 	} | ||||
|  | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) AddrPortArray(x *[8]netip.AddrPort) *binReader { | ||||
| 	for i := range x { | ||||
| 		r.AddrPort(&x[i]) | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *binReader) Error() error { | ||||
| 	return r.err | ||||
| } | ||||
| @@ -1,56 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"net/netip" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestBinWriteRead(t *testing.T) { | ||||
| 	buf := make([]byte, 1024) | ||||
|  | ||||
| 	type Item struct { | ||||
| 		Type     byte | ||||
| 		TraceID  uint64 | ||||
| 		Addrs    [8]netip.AddrPort | ||||
| 		DestAddr netip.AddrPort | ||||
| 	} | ||||
|  | ||||
| 	in := Item{ | ||||
| 		1, | ||||
| 		2, | ||||
| 		[8]netip.AddrPort{}, | ||||
| 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 22), | ||||
| 	} | ||||
|  | ||||
| 	in.Addrs[0] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{0, 1, 2, 3}), 20) | ||||
| 	in.Addrs[2] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 5}), 22) | ||||
| 	in.Addrs[3] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 3}), 23) | ||||
| 	in.Addrs[4] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 4}), 24) | ||||
| 	in.Addrs[5] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 5}), 25) | ||||
| 	in.Addrs[6] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 6}), 26) | ||||
| 	in.Addrs[7] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{7, 8, 9, 7}), 27) | ||||
|  | ||||
| 	buf = newBinWriter(buf). | ||||
| 		Byte(in.Type). | ||||
| 		Uint64(in.TraceID). | ||||
| 		AddrPort(in.DestAddr). | ||||
| 		AddrPortArray(in.Addrs). | ||||
| 		Build() | ||||
|  | ||||
| 	out := Item{} | ||||
|  | ||||
| 	err := newBinReader(buf). | ||||
| 		Byte(&out.Type). | ||||
| 		Uint64(&out.TraceID). | ||||
| 		AddrPort(&out.DestAddr). | ||||
| 		AddrPortArray(&out.Addrs). | ||||
| 		Error() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(in, out) { | ||||
| 		t.Fatal(in, out) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										130
									
								
								node/packets.go
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								node/packets.go
									
									
									
									
									
								
							| @@ -1,130 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/netip" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	errMalformedPacket   = errors.New("malformed packet") | ||||
| 	errUnknownPacketType = errors.New("unknown packet type") | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	packetTypeSyn = iota + 1 | ||||
| 	packetTypeSynAck | ||||
| 	packetTypeAck | ||||
| 	packetTypeProbe | ||||
| 	packetTypeAddrDiscovery | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type synPacket struct { | ||||
| 	TraceID uint64 // TraceID to match response w/ request. | ||||
| 	// TODO: SentAt int64 // Unixmilli. | ||||
| 	SharedKey     [32]byte // Our shared key. | ||||
| 	Direct        bool | ||||
| 	PossibleAddrs [8]netip.AddrPort // Possible public addresses of the sender. | ||||
| } | ||||
|  | ||||
| func (p synPacket) Marshal(buf []byte) []byte { | ||||
| 	return newBinWriter(buf). | ||||
| 		Byte(packetTypeSyn). | ||||
| 		Uint64(p.TraceID). | ||||
| 		SharedKey(p.SharedKey). | ||||
| 		Bool(p.Direct). | ||||
| 		AddrPort(p.PossibleAddrs[0]). | ||||
| 		AddrPort(p.PossibleAddrs[1]). | ||||
| 		AddrPort(p.PossibleAddrs[2]). | ||||
| 		AddrPort(p.PossibleAddrs[3]). | ||||
| 		AddrPort(p.PossibleAddrs[4]). | ||||
| 		AddrPort(p.PossibleAddrs[5]). | ||||
| 		AddrPort(p.PossibleAddrs[6]). | ||||
| 		AddrPort(p.PossibleAddrs[7]). | ||||
| 		Build() | ||||
| } | ||||
|  | ||||
| func parseSynPacket(buf []byte) (p synPacket, err error) { | ||||
| 	err = newBinReader(buf[1:]). | ||||
| 		Uint64(&p.TraceID). | ||||
| 		SharedKey(&p.SharedKey). | ||||
| 		Bool(&p.Direct). | ||||
| 		AddrPort(&p.PossibleAddrs[0]). | ||||
| 		AddrPort(&p.PossibleAddrs[1]). | ||||
| 		AddrPort(&p.PossibleAddrs[2]). | ||||
| 		AddrPort(&p.PossibleAddrs[3]). | ||||
| 		AddrPort(&p.PossibleAddrs[4]). | ||||
| 		AddrPort(&p.PossibleAddrs[5]). | ||||
| 		AddrPort(&p.PossibleAddrs[6]). | ||||
| 		AddrPort(&p.PossibleAddrs[7]). | ||||
| 		Error() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type ackPacket struct { | ||||
| 	TraceID       uint64 | ||||
| 	ToAddr        netip.AddrPort | ||||
| 	PossibleAddrs [8]netip.AddrPort // Possible public addresses of the sender. | ||||
| } | ||||
|  | ||||
| func (p ackPacket) Marshal(buf []byte) []byte { | ||||
| 	return newBinWriter(buf). | ||||
| 		Byte(packetTypeAck). | ||||
| 		Uint64(p.TraceID). | ||||
| 		AddrPort(p.ToAddr). | ||||
| 		AddrPort(p.PossibleAddrs[0]). | ||||
| 		AddrPort(p.PossibleAddrs[1]). | ||||
| 		AddrPort(p.PossibleAddrs[2]). | ||||
| 		AddrPort(p.PossibleAddrs[3]). | ||||
| 		AddrPort(p.PossibleAddrs[4]). | ||||
| 		AddrPort(p.PossibleAddrs[5]). | ||||
| 		AddrPort(p.PossibleAddrs[6]). | ||||
| 		AddrPort(p.PossibleAddrs[7]). | ||||
| 		Build() | ||||
|  | ||||
| } | ||||
|  | ||||
| func parseAckPacket(buf []byte) (p ackPacket, err error) { | ||||
| 	err = newBinReader(buf[1:]). | ||||
| 		Uint64(&p.TraceID). | ||||
| 		AddrPort(&p.ToAddr). | ||||
| 		AddrPort(&p.PossibleAddrs[0]). | ||||
| 		AddrPort(&p.PossibleAddrs[1]). | ||||
| 		AddrPort(&p.PossibleAddrs[2]). | ||||
| 		AddrPort(&p.PossibleAddrs[3]). | ||||
| 		AddrPort(&p.PossibleAddrs[4]). | ||||
| 		AddrPort(&p.PossibleAddrs[5]). | ||||
| 		AddrPort(&p.PossibleAddrs[6]). | ||||
| 		AddrPort(&p.PossibleAddrs[7]). | ||||
| 		Error() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // A probeReqPacket is sent from a client to a server to determine if direct | ||||
| // UDP communication can be used. | ||||
| type probePacket struct { | ||||
| 	TraceID uint64 | ||||
| } | ||||
|  | ||||
| func (p probePacket) Marshal(buf []byte) []byte { | ||||
| 	return newBinWriter(buf). | ||||
| 		Byte(packetTypeProbe). | ||||
| 		Uint64(p.TraceID). | ||||
| 		Build() | ||||
| } | ||||
|  | ||||
| func parseProbePacket(buf []byte) (p probePacket, err error) { | ||||
| 	err = newBinReader(buf[1:]). | ||||
| 		Uint64(&p.TraceID). | ||||
| 		Error() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type localDiscoveryPacket struct{} | ||||
| @@ -1 +0,0 @@ | ||||
| package node | ||||
| @@ -1,127 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/netip" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type controlPacketSender interface { | ||||
| 	SendControlPacket(pkt marshaller, route peerRoute) | ||||
| } | ||||
|  | ||||
| type dataPacketSender interface { | ||||
| 	SendDataPacket(pkt []byte, route peerRoute) | ||||
| 	SendEncryptedDataPacket(pkt []byte, addr netip.AddrPort) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type packetSender struct { | ||||
| 	conn *net.UDPConn | ||||
|  | ||||
| 	// For sending control packets. | ||||
| 	cLock sync.Mutex | ||||
| 	cBuf1 []byte | ||||
| 	cBuf2 []byte | ||||
|  | ||||
| 	// For sending data packets. | ||||
| 	dBuf1 []byte | ||||
| 	dBuf2 []byte | ||||
|  | ||||
| 	counters [256]uint64 | ||||
|  | ||||
| 	// Lock around for sending on UDP Conn. | ||||
| 	wLock sync.Mutex | ||||
| } | ||||
|  | ||||
| func newPacketSender(conn *net.UDPConn) *packetSender { | ||||
| 	ps := &packetSender{ | ||||
| 		conn:  conn, | ||||
| 		cBuf1: make([]byte, bufferSize), | ||||
| 		cBuf2: make([]byte, bufferSize), | ||||
| 		dBuf1: make([]byte, bufferSize), | ||||
| 		dBuf2: make([]byte, bufferSize), | ||||
| 	} | ||||
| 	for i := range ps.counters { | ||||
| 		ps.counters[i] = uint64(time.Now().Unix()<<30 + 1) | ||||
| 	} | ||||
| 	return ps | ||||
| } | ||||
|  | ||||
| // Safe for concurrent use. | ||||
| func (sender *packetSender) SendControlPacket(pkt marshaller, route peerRoute) { | ||||
| 	sender.cLock.Lock() | ||||
| 	defer sender.cLock.Unlock() | ||||
|  | ||||
| 	buf := pkt.Marshal(sender.cBuf1) | ||||
| 	h := header{ | ||||
| 		StreamID: controlStreamID, | ||||
| 		Counter:  atomic.AddUint64(&sender.counters[route.IP], 1), | ||||
| 		SourceIP: localIP, | ||||
| 		DestIP:   route.IP, | ||||
| 	} | ||||
| 	buf = route.ControlCipher.Encrypt(h, buf, sender.cBuf2) | ||||
|  | ||||
| 	if route.Direct { | ||||
| 		sender.writeTo(buf, route.RemoteAddr) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	sender.relayPacket(route.IP, buf, sender.cBuf1) | ||||
| } | ||||
|  | ||||
| // Not safe for concurrent use. | ||||
| func (sender *packetSender) SendDataPacket(pkt []byte, route peerRoute) { | ||||
| 	h := header{ | ||||
| 		StreamID: dataStreamID, | ||||
| 		Counter:  atomic.AddUint64(&sender.counters[route.IP], 1), | ||||
| 		SourceIP: localIP, | ||||
| 		DestIP:   route.IP, | ||||
| 	} | ||||
|  | ||||
| 	enc := route.DataCipher.Encrypt(h, pkt, sender.dBuf1) | ||||
|  | ||||
| 	if route.Direct { | ||||
| 		sender.writeTo(enc, route.RemoteAddr) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	sender.relayPacket(route.IP, enc, sender.dBuf2) | ||||
| } | ||||
|  | ||||
| func (sender *packetSender) SendEncryptedDataPacket(pkt []byte, addr netip.AddrPort) { | ||||
| 	sender.writeTo(pkt, addr) | ||||
| } | ||||
|  | ||||
| func (sender *packetSender) relayPacket(destIP byte, data, buf []byte) { | ||||
| 	ip := relayIP.Load() | ||||
| 	if ip == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	relayRoute := routingTable[*ip].Load() | ||||
| 	if relayRoute == nil || !relayRoute.Up || !relayRoute.Relay { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	h := header{ | ||||
| 		StreamID: dataStreamID, | ||||
| 		Counter:  atomic.AddUint64(&sender.counters[relayRoute.IP], 1), | ||||
| 		SourceIP: localIP, | ||||
| 		DestIP:   destIP, | ||||
| 	} | ||||
|  | ||||
| 	enc := relayRoute.DataCipher.Encrypt(h, data, buf) | ||||
| 	sender.writeTo(enc, relayRoute.RemoteAddr) | ||||
| } | ||||
|  | ||||
| func (sender *packetSender) writeTo(packet []byte, addr netip.AddrPort) { | ||||
| 	sender.wLock.Lock() | ||||
| 	if _, err := sender.conn.WriteToUDPAddrPort(packet, addr); err != nil { | ||||
| 		log.Printf("Failed to write to UDP port: %v", err) | ||||
| 	} | ||||
| 	sender.wLock.Unlock() | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // TODO: Make part of main loop on ping timer | ||||
| func relayManager() { | ||||
| 	time.Sleep(2 * time.Second) | ||||
| 	updateRelayRoute() | ||||
|  | ||||
| 	for range time.Tick(8 * time.Second) { | ||||
| 		relay := getRelayRoute() | ||||
| 		if relay == nil || !relay.Up || !relay.Relay { | ||||
| 			updateRelayRoute() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func updateRelayRoute() { | ||||
| 	possible := make([]*peerRoute, 0, 8) | ||||
| 	for i := range routingTable { | ||||
| 		route := routingTable[i].Load() | ||||
| 		if !route.Up || !route.Relay { | ||||
| 			continue | ||||
| 		} | ||||
| 		possible = append(possible, route) | ||||
| 	} | ||||
|  | ||||
| 	if len(possible) == 0 { | ||||
| 		log.Printf("No relay available.") | ||||
| 		relayIP.Store(nil) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ip := possible[rand.Intn(len(possible))].IP | ||||
| 	log.Printf("New relay IP: %d", ip) | ||||
| 	relayIP.Store(&ip) | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"net/netip" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
|  | ||||
| type sharedState struct { | ||||
| 	// Immutable: | ||||
| 	HubAddress  string | ||||
| 	APIKey      string | ||||
| 	NetName     string | ||||
| 	LocalIP     byte | ||||
| 	LocalPub    bool | ||||
| 	LocalAddr   netip.AddrPort | ||||
| 	PrivKey     []byte | ||||
| 	PrivSignKey []byte | ||||
|  | ||||
| 	// Mutable: | ||||
| 	Routes  [256]*atomic.Pointer[peerRoute] | ||||
| 	RelayIP *atomic.Pointer[byte] | ||||
|  | ||||
| 	// Messages for supervisor main loop. | ||||
| 	Messages chan any | ||||
| } | ||||
|  | ||||
| func newSharedState( | ||||
| 	netName, | ||||
| 	hubAddress, | ||||
| 	apiKey string, | ||||
| 	conf localConfig, | ||||
| ) ( | ||||
| 	ss sharedState, | ||||
| ) { | ||||
| 	ss.HubAddress = hubAddress | ||||
|  | ||||
| 	ss.APIKey = apiKey | ||||
| 	ss.NetName = netName | ||||
| 	ss.LocalIP = conf.PeerIP | ||||
|  | ||||
| 	ip, ok := netip.AddrFromSlice(conf.PublicIP) | ||||
| 	if ok { | ||||
| 		ss.LocalPub = true | ||||
| 		ss.LocalAddr = netip.AddrPortFrom(ip, conf.Port) | ||||
| 	} | ||||
|  | ||||
| 	ss.PrivKey = conf.PrivKey | ||||
| 	ss.PrivSignKey = conf.PrivSignKey | ||||
|  | ||||
| 	for i := range ss.Routes { | ||||
| 		ss.Routes[i] = &atomic.Pointer[peerRoute]{} | ||||
| 		ss.Routes[i].Store(&peerRoute{}) | ||||
| 	} | ||||
|  | ||||
| 	ss.RelayIP = &atomic.Pointer[byte]{} | ||||
|  | ||||
| 	ss.Messages = make(chan any, 1024) | ||||
| 	return | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import "vppn/m" | ||||
|  | ||||
| // TODO: | ||||
| var sharedStateForTesting = func() sharedState { | ||||
| 	ss := newSharedState( | ||||
| 		"testNet", | ||||
| 		"http://localhost:39499", | ||||
| 		"123", | ||||
| 		localConfig{ | ||||
| 			PeerConfig: m.PeerConfig{}, | ||||
| 		}) | ||||
|  | ||||
| 	return ss | ||||
| } | ||||
| @@ -1,421 +0,0 @@ | ||||
| package node | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/netip" | ||||
| 	"strings" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| 	"vppn/m" | ||||
|  | ||||
| 	"git.crumpington.com/lib/go/ratelimiter" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	pingInterval    = 8 * time.Second | ||||
| 	timeoutInterval = 30 * time.Second | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func startPeerSuper( | ||||
| 	routingTable [256]*atomic.Pointer[peerRoute], | ||||
| 	messages chan any, | ||||
| 	sender controlPacketSender, | ||||
| ) { | ||||
| 	peers := [256]peerState{} | ||||
| 	for i := range peers { | ||||
| 		data := &peerStateData{ | ||||
| 			sender:    sender, | ||||
| 			published: routingTable[i], | ||||
| 			remoteIP:  byte(i), | ||||
| 			limiter: ratelimiter.New(ratelimiter.Config{ | ||||
| 				FillPeriod:   20 * time.Millisecond, | ||||
| 				MaxWaitCount: 1, | ||||
| 			}), | ||||
| 		} | ||||
| 		peers[i] = data.OnPeerUpdate(nil) | ||||
| 	} | ||||
| 	go runPeerSuper(peers, messages) | ||||
| } | ||||
|  | ||||
| func runPeerSuper(peers [256]peerState, messages chan any) { | ||||
| 	for raw := range messages { | ||||
| 		switch msg := raw.(type) { | ||||
|  | ||||
| 		case peerUpdateMsg: | ||||
| 			peers[msg.PeerIP] = peers[msg.PeerIP].OnPeerUpdate(msg.Peer) | ||||
|  | ||||
| 		case controlMsg[synPacket]: | ||||
| 			peers[msg.SrcIP].OnSyn(msg) | ||||
|  | ||||
| 		case controlMsg[ackPacket]: | ||||
| 			peers[msg.SrcIP].OnAck(msg) | ||||
|  | ||||
| 		case controlMsg[probePacket]: | ||||
| 			peers[msg.SrcIP].OnProbe(msg) | ||||
|  | ||||
| 		case controlMsg[localDiscoveryPacket]: | ||||
| 			peers[msg.SrcIP].OnLocalDiscovery(msg) | ||||
|  | ||||
| 		case pingTimerMsg: | ||||
| 			publicAddrs.Clean() | ||||
| 			for i := range peers { | ||||
| 				if newState := peers[i].OnPingTimer(); newState != nil { | ||||
| 					peers[i] = newState | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		default: | ||||
| 			log.Printf("WARNING: unknown message type: %+v", msg) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type peerState interface { | ||||
| 	OnPeerUpdate(*m.Peer) peerState | ||||
| 	OnSyn(controlMsg[synPacket]) | ||||
| 	OnAck(controlMsg[ackPacket]) | ||||
| 	OnProbe(controlMsg[probePacket]) | ||||
| 	OnLocalDiscovery(controlMsg[localDiscoveryPacket]) | ||||
| 	OnPingTimer() peerState | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type peerStateData struct { | ||||
| 	sender controlPacketSender | ||||
|  | ||||
| 	// The purpose of this state machine is to manage this published data. | ||||
| 	published *atomic.Pointer[peerRoute] | ||||
| 	staged    peerRoute // Local copy of shared data. See publish(). | ||||
|  | ||||
| 	// Immutable data. | ||||
| 	remoteIP byte // Remote VPN IP. | ||||
|  | ||||
| 	// Mutable peer data. | ||||
| 	peer      *m.Peer | ||||
| 	remotePub bool | ||||
|  | ||||
| 	// For logging. Set per-state. | ||||
| 	client bool | ||||
|  | ||||
| 	// We rate limit per remote endpoint because if we don't we tend to lose | ||||
| 	// packets. | ||||
| 	limiter *ratelimiter.Limiter | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func (s *peerStateData) sendControlPacket(pkt interface{ Marshal([]byte) []byte }) { | ||||
| 	s._sendControlPacket(pkt, s.staged) | ||||
| } | ||||
|  | ||||
| func (s *peerStateData) sendControlPacketTo(pkt interface{ Marshal([]byte) []byte }, addr netip.AddrPort) { | ||||
| 	if !addr.IsValid() { | ||||
| 		s.logf("ERROR: Attepted to send packet to invalid address: %v", addr) | ||||
| 		return | ||||
| 	} | ||||
| 	route := s.staged | ||||
| 	route.Direct = true | ||||
| 	route.RemoteAddr = addr | ||||
| 	s._sendControlPacket(pkt, route) | ||||
| } | ||||
|  | ||||
| func (s *peerStateData) _sendControlPacket(pkt interface{ Marshal([]byte) []byte }, route peerRoute) { | ||||
| 	if err := s.limiter.Limit(); err != nil { | ||||
| 		s.logf("Not sending control packet: rate limited.") // Shouldn't happen. | ||||
| 		return | ||||
| 	} | ||||
| 	s.sender.SendControlPacket(pkt, route) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func (s *peerStateData) publish() { | ||||
| 	data := s.staged | ||||
| 	s.published.Store(&data) | ||||
| } | ||||
|  | ||||
| func (s *peerStateData) logf(format string, args ...any) { | ||||
| 	b := strings.Builder{} | ||||
| 	b.WriteString(fmt.Sprintf("%30s: ", s.peer.Name)) | ||||
|  | ||||
| 	if s.client { | ||||
| 		b.WriteString("CLIENT | ") | ||||
| 	} else { | ||||
| 		b.WriteString("SERVER | ") | ||||
| 	} | ||||
|  | ||||
| 	if s.staged.Direct { | ||||
| 		b.WriteString("DIRECT  | ") | ||||
| 	} else { | ||||
| 		b.WriteString("RELAYED | ") | ||||
| 	} | ||||
|  | ||||
| 	if s.staged.Up { | ||||
| 		b.WriteString("UP   | ") | ||||
| 	} else { | ||||
| 		b.WriteString("DOWN | ") | ||||
| 	} | ||||
|  | ||||
| 	log.Printf(b.String()+format, args...) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func (s *peerStateData) OnPeerUpdate(peer *m.Peer) peerState { | ||||
| 	defer s.publish() | ||||
|  | ||||
| 	if peer == nil { | ||||
| 		return enterStateDisconnected(s) | ||||
| 	} | ||||
|  | ||||
| 	s.peer = peer | ||||
| 	s.staged = peerRoute{ | ||||
| 		IP:         s.remoteIP, | ||||
| 		PubSignKey: peer.PubSignKey, | ||||
| 		// TODO: privKey global. | ||||
| 		ControlCipher: newControlCipher(privKey, peer.PubKey), | ||||
| 		DataCipher:    newDataCipher(), | ||||
| 	} | ||||
| 	s.remotePub = false | ||||
|  | ||||
| 	if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid { | ||||
| 		s.remotePub = true | ||||
| 		s.staged.Relay = peer.Relay | ||||
| 		s.staged.Direct = true | ||||
| 		s.staged.RemoteAddr = netip.AddrPortFrom(ip, peer.Port) | ||||
| 	} else if localPub { | ||||
| 		s.staged.Direct = true | ||||
| 	} | ||||
|  | ||||
| 	if s.remotePub == localPub { | ||||
| 		// TODO: localIP is global | ||||
| 		if localIP < s.remoteIP { | ||||
| 			return enterStateServer(s) | ||||
| 		} | ||||
| 		return enterStateClient(s) | ||||
| 	} | ||||
|  | ||||
| 	if s.remotePub { | ||||
| 		return enterStateClient(s) | ||||
| 	} | ||||
| 	return enterStateServer(s) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type stateDisconnected struct { | ||||
| 	*peerStateData | ||||
| } | ||||
|  | ||||
| func enterStateDisconnected(s *peerStateData) peerState { | ||||
| 	s.peer = nil | ||||
| 	s.staged = peerRoute{} | ||||
| 	s.publish() | ||||
| 	return &stateDisconnected{s} | ||||
| } | ||||
|  | ||||
| func (s *stateDisconnected) OnSyn(controlMsg[synPacket])                       {} | ||||
| func (s *stateDisconnected) OnAck(controlMsg[ackPacket])                       {} | ||||
| func (s *stateDisconnected) OnProbe(controlMsg[probePacket])                   {} | ||||
| func (s *stateDisconnected) OnLocalDiscovery(controlMsg[localDiscoveryPacket]) {} | ||||
|  | ||||
| func (s *stateDisconnected) OnPingTimer() peerState { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type stateServer struct { | ||||
| 	*stateDisconnected | ||||
| 	lastSeen   time.Time | ||||
| 	synTraceID uint64 | ||||
| } | ||||
|  | ||||
| func enterStateServer(s *peerStateData) peerState { | ||||
| 	s.client = false | ||||
| 	return &stateServer{stateDisconnected: &stateDisconnected{s}} | ||||
| } | ||||
|  | ||||
| func (s *stateServer) OnSyn(msg controlMsg[synPacket]) { | ||||
| 	s.lastSeen = time.Now() | ||||
| 	p := msg.Packet | ||||
|  | ||||
| 	// Before we can respond to this packet, we need to make sure the | ||||
| 	// route is setup properly. | ||||
| 	// | ||||
| 	// The client will update the syn's TraceID whenever there's a change. | ||||
| 	// The server will follow the client's request. | ||||
| 	if p.TraceID != s.synTraceID || !s.staged.Up { | ||||
| 		s.synTraceID = p.TraceID | ||||
| 		s.staged.Up = true | ||||
| 		s.staged.Direct = p.Direct | ||||
| 		s.staged.DataCipher = newDataCipherFromKey(p.SharedKey) | ||||
| 		s.staged.RemoteAddr = msg.SrcAddr | ||||
| 		s.publish() | ||||
| 		s.logf("Got syn.") | ||||
| 	} | ||||
|  | ||||
| 	// Always respond. | ||||
| 	ack := ackPacket{ | ||||
| 		TraceID:       p.TraceID, | ||||
| 		ToAddr:        s.staged.RemoteAddr, | ||||
| 		PossibleAddrs: publicAddrs.Get(), | ||||
| 	} | ||||
| 	s.sendControlPacket(ack) | ||||
|  | ||||
| 	if s.staged.Direct { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Not direct => send probes. | ||||
| 	for _, addr := range p.PossibleAddrs { | ||||
| 		if !addr.IsValid() { | ||||
| 			break | ||||
| 		} | ||||
| 		s.sendControlPacketTo(probePacket{TraceID: newTraceID()}, addr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *stateServer) OnProbe(msg controlMsg[probePacket]) { | ||||
| 	if !msg.SrcAddr.IsValid() { | ||||
| 		s.logf("Invalid probe address.") | ||||
| 		return | ||||
| 	} | ||||
| 	s.sendControlPacketTo(probePacket{TraceID: msg.Packet.TraceID}, msg.SrcAddr) | ||||
| } | ||||
|  | ||||
| func (s *stateServer) OnPingTimer() peerState { | ||||
| 	if time.Since(s.lastSeen) > timeoutInterval && s.staged.Up { | ||||
| 		s.staged.Up = false | ||||
| 		s.publish() | ||||
| 		s.logf("Connection timeout.") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| type stateClient struct { | ||||
| 	*stateDisconnected | ||||
|  | ||||
| 	lastSeen time.Time | ||||
| 	syn      synPacket | ||||
| 	ack      ackPacket | ||||
|  | ||||
| 	probes             map[uint64]netip.AddrPort | ||||
| 	localDiscoveryAddr netip.AddrPort | ||||
| } | ||||
|  | ||||
| func enterStateClient(s *peerStateData) peerState { | ||||
| 	s.client = true | ||||
| 	ss := &stateClient{ | ||||
| 		stateDisconnected: &stateDisconnected{s}, | ||||
| 		probes:            map[uint64]netip.AddrPort{}, | ||||
| 	} | ||||
|  | ||||
| 	ss.syn = synPacket{ | ||||
| 		TraceID:       newTraceID(), | ||||
| 		SharedKey:     s.staged.DataCipher.Key(), | ||||
| 		Direct:        s.staged.Direct, | ||||
| 		PossibleAddrs: publicAddrs.Get(), | ||||
| 	} | ||||
| 	ss.sendControlPacket(ss.syn) | ||||
|  | ||||
| 	return ss | ||||
| } | ||||
|  | ||||
| func (s *stateClient) sendProbeTo(addr netip.AddrPort) { | ||||
| 	probe := probePacket{TraceID: newTraceID()} | ||||
| 	s.probes[probe.TraceID] = addr | ||||
| 	s.sendControlPacketTo(probe, addr) | ||||
| } | ||||
|  | ||||
| func (s *stateClient) OnAck(msg controlMsg[ackPacket]) { | ||||
| 	if msg.Packet.TraceID != s.syn.TraceID { | ||||
| 		s.logf("Ack has incorrect trace ID") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	s.ack = msg.Packet | ||||
| 	s.lastSeen = time.Now() | ||||
|  | ||||
| 	if !s.staged.Up { | ||||
| 		s.staged.Up = true | ||||
| 		s.logf("Got ack.") | ||||
| 		s.publish() | ||||
| 	} | ||||
|  | ||||
| 	// Store possible public address if we're not a public node. | ||||
| 	// TODO: localPub is global, publicAddrs is global. | ||||
| 	if !localPub && s.remotePub { | ||||
| 		publicAddrs.Store(msg.Packet.ToAddr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *stateClient) OnProbe(msg controlMsg[probePacket]) { | ||||
| 	if s.staged.Direct { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	addr, ok := s.probes[msg.Packet.TraceID] | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	s.staged.RemoteAddr = addr | ||||
| 	s.staged.Direct = true | ||||
| 	s.publish() | ||||
|  | ||||
| 	s.syn.TraceID = newTraceID() | ||||
| 	s.syn.Direct = true | ||||
| 	s.syn.PossibleAddrs = [8]netip.AddrPort{} | ||||
| 	s.sendControlPacket(s.syn) | ||||
|  | ||||
| 	s.logf("Established direct connection to %s.", s.staged.RemoteAddr.String()) | ||||
| } | ||||
|  | ||||
| func (s *stateClient) OnLocalDiscovery(msg controlMsg[localDiscoveryPacket]) { | ||||
| 	if s.staged.Direct { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// The source port will be the multicast port, so we'll have to | ||||
| 	// construct the correct address using the peer's listed port. | ||||
| 	s.localDiscoveryAddr = netip.AddrPortFrom(msg.SrcAddr.Addr(), s.peer.Port) | ||||
| } | ||||
|  | ||||
| func (s *stateClient) OnPingTimer() peerState { | ||||
| 	if time.Since(s.lastSeen) > timeoutInterval { | ||||
| 		if s.staged.Up { | ||||
| 			s.logf("Connection timeout.") | ||||
| 		} | ||||
| 		return s.OnPeerUpdate(s.peer) | ||||
| 	} | ||||
|  | ||||
| 	s.sendControlPacket(s.syn) | ||||
|  | ||||
| 	if s.staged.Direct { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	clear(s.probes) | ||||
| 	for _, addr := range s.ack.PossibleAddrs { | ||||
| 		if !addr.IsValid() { | ||||
| 			break | ||||
| 		} | ||||
| 		s.sendProbeTo(addr) | ||||
| 	} | ||||
|  | ||||
| 	if s.localDiscoveryAddr.IsValid() { | ||||
| 		s.sendProbeTo(s.localDiscoveryAddr) | ||||
| 		s.localDiscoveryAddr = netip.AddrPort{} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user