WIP: client/server/relay working.
This commit is contained in:
parent
a263f65c5d
commit
08f11ce82b
@ -1,9 +1,15 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
|
import "net/netip"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bufferSize = 1536
|
bufferSize = 1536
|
||||||
if_mtu = 1200
|
if_mtu = 1400
|
||||||
if_queue_len = 2048
|
if_queue_len = 2048
|
||||||
controlCipherOverhead = 16
|
controlCipherOverhead = 16
|
||||||
dataCipherOverhead = 16
|
dataCipherOverhead = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
zeroAddrPort = netip.AddrPort{}
|
||||||
|
)
|
||||||
|
@ -3,8 +3,6 @@ package node
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -31,10 +29,6 @@ type controlPacket struct {
|
|||||||
|
|
||||||
func (p *controlPacket) ParsePayload(buf []byte) (err error) {
|
func (p *controlPacket) ParsePayload(buf []byte) (err error) {
|
||||||
switch buf[0] {
|
switch buf[0] {
|
||||||
case packetTypePing:
|
|
||||||
p.Payload, err = parsePingPacket(buf)
|
|
||||||
case packetTypePong:
|
|
||||||
p.Payload, err = parsePongPacket(buf)
|
|
||||||
case packetTypeSyn:
|
case packetTypeSyn:
|
||||||
p.Payload, err = parseSynPacket(buf)
|
p.Payload, err = parseSynPacket(buf)
|
||||||
case packetTypeSynAck:
|
case packetTypeSynAck:
|
||||||
@ -52,7 +46,6 @@ func (p *controlPacket) ParsePayload(buf []byte) (err error) {
|
|||||||
type synPacket struct {
|
type synPacket struct {
|
||||||
TraceID uint64 // TraceID to match response w/ request.
|
TraceID uint64 // TraceID to match response w/ request.
|
||||||
SharedKey [32]byte // Our shared key.
|
SharedKey [32]byte // Our shared key.
|
||||||
ServerAddr netip.AddrPort // The address we're sending to.
|
|
||||||
RelayIP byte
|
RelayIP byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +54,6 @@ func (p synPacket) Marshal(buf []byte) []byte {
|
|||||||
Byte(packetTypeSyn).
|
Byte(packetTypeSyn).
|
||||||
Uint64(p.TraceID).
|
Uint64(p.TraceID).
|
||||||
SharedKey(p.SharedKey).
|
SharedKey(p.SharedKey).
|
||||||
AddrPort(p.ServerAddr).
|
|
||||||
Byte(p.RelayIP).
|
Byte(p.RelayIP).
|
||||||
Build()
|
Build()
|
||||||
}
|
}
|
||||||
@ -70,7 +62,6 @@ func parseSynPacket(buf []byte) (p synPacket, err error) {
|
|||||||
err = newBinReader(buf[1:]).
|
err = newBinReader(buf[1:]).
|
||||||
Uint64(&p.TraceID).
|
Uint64(&p.TraceID).
|
||||||
SharedKey(&p.SharedKey).
|
SharedKey(&p.SharedKey).
|
||||||
AddrPort(&p.ServerAddr).
|
|
||||||
Byte(&p.RelayIP).
|
Byte(&p.RelayIP).
|
||||||
Error()
|
Error()
|
||||||
return
|
return
|
||||||
@ -119,63 +110,3 @@ func parseAckPacket(buf []byte) (p ackPacket, err error) {
|
|||||||
Error()
|
Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// A pingPacket is sent from a node acting as a client, to a node acting
|
|
||||||
// as a server. It always contains the shared key the client is expecting
|
|
||||||
// to use for data encryption with the server.
|
|
||||||
type pingPacket struct {
|
|
||||||
SentAt int64 // UnixMilli. // Not used. Use traceID.
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPingPacket() (pp pingPacket) {
|
|
||||||
pp.SentAt = time.Now().UnixMilli()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p pingPacket) Marshal(buf []byte) []byte {
|
|
||||||
return newBinWriter(buf).
|
|
||||||
Byte(packetTypePing).
|
|
||||||
Int64(p.SentAt).
|
|
||||||
Build()
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePingPacket(buf []byte) (p pingPacket, err error) {
|
|
||||||
err = newBinReader(buf[1:]).
|
|
||||||
Int64(&p.SentAt).
|
|
||||||
Error()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// A pongPacket is sent by a node in a server role in response to a pingPacket.
|
|
||||||
type pongPacket struct {
|
|
||||||
SentAt int64 // UnixMilli.
|
|
||||||
RecvdAt int64 // UnixMilli.
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPongPacket(sentAt int64) (pp pongPacket) {
|
|
||||||
pp.SentAt = sentAt
|
|
||||||
pp.RecvdAt = time.Now().UnixMilli()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p pongPacket) Marshal(buf []byte) []byte {
|
|
||||||
buf = buf[:17]
|
|
||||||
buf[0] = packetTypePong
|
|
||||||
*(*uint64)(unsafe.Pointer(&buf[1])) = uint64(p.SentAt)
|
|
||||||
*(*uint64)(unsafe.Pointer(&buf[9])) = uint64(p.RecvdAt)
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePongPacket(buf []byte) (p pongPacket, err error) {
|
|
||||||
if len(buf) != 17 {
|
|
||||||
return p, errMalformedPacket
|
|
||||||
}
|
|
||||||
p.SentAt = *(*int64)(unsafe.Pointer(&buf[1]))
|
|
||||||
p.RecvdAt = *(*int64)(unsafe.Pointer(&buf[9]))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,6 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"net/netip"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -10,8 +9,6 @@ import (
|
|||||||
func TestPacketSyn(t *testing.T) {
|
func TestPacketSyn(t *testing.T) {
|
||||||
in := synPacket{
|
in := synPacket{
|
||||||
TraceID: newTraceID(),
|
TraceID: newTraceID(),
|
||||||
Direct: true,
|
|
||||||
ServerAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 34),
|
|
||||||
}
|
}
|
||||||
rand.Read(in.SharedKey[:])
|
rand.Read(in.SharedKey[:])
|
||||||
|
|
||||||
@ -54,38 +51,3 @@ func TestPacketAck(t *testing.T) {
|
|||||||
t.Fatal("\n", in, "\n", out)
|
t.Fatal("\n", in, "\n", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPacketPing(t *testing.T) {
|
|
||||||
sharedKey := make([]byte, 32)
|
|
||||||
rand.Read(sharedKey)
|
|
||||||
|
|
||||||
buf := make([]byte, bufferSize)
|
|
||||||
|
|
||||||
p := newPingPacket([32]byte(sharedKey))
|
|
||||||
out := p.Marshal(buf)
|
|
||||||
|
|
||||||
p2, err := parsePingPacket(out)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(p, p2) {
|
|
||||||
t.Fatal(p, p2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPacketPong(t *testing.T) {
|
|
||||||
buf := make([]byte, bufferSize)
|
|
||||||
|
|
||||||
p := newPongPacket(123566)
|
|
||||||
out := p.Marshal(buf)
|
|
||||||
|
|
||||||
p2, err := parsePongPacket(out)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(p, p2) {
|
|
||||||
t.Fatal(p, p2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,405 +0,0 @@
|
|||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
"net/netip"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
"vppn/m"
|
|
||||||
)
|
|
||||||
|
|
||||||
type peerState interface {
|
|
||||||
Name() string
|
|
||||||
OnSyn(netip.AddrPort, synPacket) peerState
|
|
||||||
OnSynAck(netip.AddrPort, synAckPacket) peerState
|
|
||||||
OnAck(netip.AddrPort, ackPacket) peerState
|
|
||||||
|
|
||||||
OnPingTimer() peerState
|
|
||||||
OnTimeoutTimer() peerState
|
|
||||||
|
|
||||||
// When the peer is updated, we reset. Handled by base state.
|
|
||||||
OnPeerUpdate(*m.Peer) peerState
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateBase struct {
|
|
||||||
// The purpose of this state machine is to manage this published data.
|
|
||||||
published *atomic.Pointer[peerRoutingData]
|
|
||||||
staged peerRoutingData // Local copy of shared data. See publish().
|
|
||||||
|
|
||||||
// The other remote peers.
|
|
||||||
peers *remotePeers
|
|
||||||
|
|
||||||
// Immutable data.
|
|
||||||
localIP byte
|
|
||||||
localPub bool
|
|
||||||
remoteIP byte
|
|
||||||
privKey []byte
|
|
||||||
conn *connWriter
|
|
||||||
|
|
||||||
// For sending to peer.
|
|
||||||
counter *uint64
|
|
||||||
|
|
||||||
// Mutable peer data.
|
|
||||||
peer *m.Peer
|
|
||||||
remotePub bool
|
|
||||||
|
|
||||||
// Timers
|
|
||||||
pingTimer *time.Timer
|
|
||||||
timeoutTimer *time.Timer
|
|
||||||
|
|
||||||
buf []byte
|
|
||||||
encBuf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb *stateBase) Name() string { return "idle" }
|
|
||||||
|
|
||||||
func (s *stateBase) OnPeerUpdate(peer *m.Peer) peerState {
|
|
||||||
// Both nil: no change.
|
|
||||||
if peer == nil && s.peer == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// No change.
|
|
||||||
if peer != nil && s.peer != nil && s.peer.Version == peer.Version {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.selectStateFromPeer(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateBase) selectStateFromPeer(peer *m.Peer) peerState {
|
|
||||||
s.peer = peer
|
|
||||||
s.staged = peerRoutingData{}
|
|
||||||
defer s.publish()
|
|
||||||
|
|
||||||
if peer == nil {
|
|
||||||
return newStateNoPeer(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.staged.controlCipher = newControlCipher(s.privKey, peer.EncPubKey)
|
|
||||||
s.staged.dataCipher = newDataCipher()
|
|
||||||
|
|
||||||
s.resetPingTimer()
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
|
|
||||||
ip, isValid := netip.AddrFromSlice(peer.PublicIP)
|
|
||||||
if isValid {
|
|
||||||
s.remotePub = true
|
|
||||||
s.staged.remoteAddr = netip.AddrPortFrom(ip, peer.Port)
|
|
||||||
s.staged.relay = peer.Mediator
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.remotePub == s.localPub {
|
|
||||||
if s.localIP < s.remoteIP {
|
|
||||||
return newStateServer2(s)
|
|
||||||
}
|
|
||||||
return newStateDialLocal(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.remotePub {
|
|
||||||
return newStateDialLocal(s)
|
|
||||||
}
|
|
||||||
return newStateServer2(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateBase) OnSyn(rAddr netip.AddrPort, p synPacket) peerState { return nil }
|
|
||||||
func (s *stateBase) OnSynAck(rAddr netip.AddrPort, p synAckPacket) peerState { return nil }
|
|
||||||
func (s *stateBase) OnAck(rAddr netip.AddrPort, p ackPacket) peerState { return nil }
|
|
||||||
|
|
||||||
func (s *stateBase) OnPingTimer() peerState { return nil }
|
|
||||||
func (s *stateBase) OnTimeoutTimer() peerState { return nil }
|
|
||||||
|
|
||||||
// Helpers.
|
|
||||||
|
|
||||||
func (s *stateBase) resetPingTimer() { s.pingTimer.Reset(pingInterval) }
|
|
||||||
func (s *stateBase) resetTimeoutTimer() { s.timeoutTimer.Reset(timeoutInterval) }
|
|
||||||
func (s *stateBase) stopPingTimer() { s.pingTimer.Stop() }
|
|
||||||
func (s *stateBase) stopTimeoutTimer() { s.timeoutTimer.Stop() }
|
|
||||||
|
|
||||||
func (s *stateBase) logf(msg string, args ...any) {
|
|
||||||
log.Printf(fmt.Sprintf("[%03d] ", s.remoteIP)+msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateBase) publish() {
|
|
||||||
data := s.staged
|
|
||||||
s.published.Store(&data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateBase) selectRelay() byte {
|
|
||||||
possible := make([]byte, 0, 8)
|
|
||||||
for i, peer := range s.peers {
|
|
||||||
if peer.CanRelay() {
|
|
||||||
possible = append(possible, byte(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(possible) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return possible[rand.Intn(len(possible))]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateBase) sendControlPacket(pkt interface{ Marshal([]byte) []byte }) {
|
|
||||||
buf := pkt.Marshal(s.buf)
|
|
||||||
h := header{
|
|
||||||
StreamID: controlStreamID,
|
|
||||||
Counter: atomic.AddUint64(s.counter, 1),
|
|
||||||
SourceIP: s.localIP,
|
|
||||||
DestIP: s.remoteIP,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = s.staged.controlCipher.Encrypt(h, buf, s.encBuf)
|
|
||||||
if s.staged.relayIP != 0 {
|
|
||||||
s.peers[s.staged.relayIP].RelayFor(s.remoteIP, buf)
|
|
||||||
} else {
|
|
||||||
s.conn.WriteTo(buf, s.staged.remoteAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateNoPeer struct{ *stateBase }
|
|
||||||
|
|
||||||
func newStateNoPeer(b *stateBase) *stateNoPeer {
|
|
||||||
s := &stateNoPeer{b}
|
|
||||||
s.pingTimer.Stop()
|
|
||||||
s.timeoutTimer.Stop()
|
|
||||||
s.publish()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateServer2 struct {
|
|
||||||
*stateBase
|
|
||||||
syn synPacket
|
|
||||||
publishedTraceID uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Server should send SynAck packets on a loop.
|
|
||||||
func newStateServer2(b *stateBase) peerState {
|
|
||||||
s := &stateServer2{stateBase: b}
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateServer2) Name() string { return "server" }
|
|
||||||
|
|
||||||
func (s *stateServer2) OnSyn(remoteAddr netip.AddrPort, p synPacket) peerState {
|
|
||||||
s.syn = p
|
|
||||||
s.sendControlPacket(newSynAckPacket(p.TraceID))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateServer2) OnAck(remoteAddr netip.AddrPort, p ackPacket) peerState {
|
|
||||||
if p.TraceID != s.syn.TraceID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
|
|
||||||
if p.TraceID == s.publishedTraceID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pubish staged
|
|
||||||
s.staged.remoteAddr = remoteAddr
|
|
||||||
s.staged.dataCipher = newDataCipherFromKey(s.syn.SharedKey)
|
|
||||||
s.staged.relayIP = s.syn.RelayIP
|
|
||||||
s.staged.up = true
|
|
||||||
s.publish()
|
|
||||||
|
|
||||||
s.publishedTraceID = p.TraceID
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateServer) OnTimeoutTimer() peerState {
|
|
||||||
// TODO: We're down.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateDialLocal struct {
|
|
||||||
*stateBase
|
|
||||||
syn synPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateDialLocal(b *stateBase) peerState {
|
|
||||||
// s := stateDialLocal{stateBase: b}
|
|
||||||
// TODO: check for peer local address.
|
|
||||||
return newStateDialDirect(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateDialLocal) Name() string { return "dial-local" }
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateDialDirect struct {
|
|
||||||
*stateBase
|
|
||||||
syn synPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateDialDirect(b *stateBase) peerState {
|
|
||||||
// If we don't have an address, dial via relay.
|
|
||||||
if b.staged.remoteAddr == zeroAddrPort {
|
|
||||||
return newStateNoPeer(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &stateDialDirect{stateBase: b}
|
|
||||||
s.syn = synPacket{
|
|
||||||
TraceID: newTraceID(),
|
|
||||||
SharedKey: s.staged.dataCipher.Key(),
|
|
||||||
ServerAddr: b.staged.remoteAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sendControlPacket(s.syn)
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateDialDirect) Name() string { return "dial-direct" }
|
|
||||||
|
|
||||||
func (s *stateDialDirect) OnSynAck(remoteAddr netip.AddrPort, p synAckPacket) peerState {
|
|
||||||
if p.TraceID != s.syn.TraceID {
|
|
||||||
// Hmm...
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sendControlPacket(ackPacket{TraceID: s.syn.TraceID})
|
|
||||||
s.logf("GOT SYN-ACK! TODO!")
|
|
||||||
// client should continue to respond to synAck packets from server.
|
|
||||||
// return newStateClientConnected(s.stateBase, s.syn.TraceID) ...
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateDialDirect) OnTimeoutTimer() peerState {
|
|
||||||
s.logf("Timeout when dialing")
|
|
||||||
return newStateDialLocal(s.stateBase)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateClient struct {
|
|
||||||
sharedKey [32]byte
|
|
||||||
*stateBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateClient(b *stateBase) peerState {
|
|
||||||
s := &stateClient{stateBase: b}
|
|
||||||
s.publish()
|
|
||||||
|
|
||||||
s.staged.dataCipher = newDataCipher()
|
|
||||||
s.sharedKey = s.staged.dataCipher.Key()
|
|
||||||
|
|
||||||
s.sendControlPacket(newPingPacket())
|
|
||||||
s.resetPingTimer()
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateClient) Name() string { return "client" }
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateServer struct {
|
|
||||||
*stateBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateServer(b *stateBase) peerState {
|
|
||||||
s := &stateServer{b}
|
|
||||||
s.publish()
|
|
||||||
s.stopPingTimer()
|
|
||||||
s.stopTimeoutTimer()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateServer) Name() string { return "server" }
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateSelectRelay struct {
|
|
||||||
*stateBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateSelectRelay(b *stateBase) peerState {
|
|
||||||
s := &stateSelectRelay{stateBase: b}
|
|
||||||
s.staged.dataCipher = nil
|
|
||||||
s.staged.up = false
|
|
||||||
s.publish()
|
|
||||||
|
|
||||||
if relay := s.selectRelay(); relay != 0 {
|
|
||||||
s.staged.up = false
|
|
||||||
s.staged.relayIP = relay
|
|
||||||
return s.selectRole()
|
|
||||||
}
|
|
||||||
|
|
||||||
s.resetPingTimer()
|
|
||||||
s.stopTimeoutTimer()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateSelectRelay) selectRole() peerState {
|
|
||||||
if s.localIP < s.remoteIP {
|
|
||||||
return newStateServerRelayed(s.stateBase)
|
|
||||||
}
|
|
||||||
return newStateClientRelayed(s.stateBase)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateSelectRelay) Name() string { return "select-relay" }
|
|
||||||
|
|
||||||
func (s *stateSelectRelay) OnPingTimer() peerState {
|
|
||||||
if relay := s.selectRelay(); relay != 0 {
|
|
||||||
s.logf("Got relay IP: %d", relay)
|
|
||||||
s.staged.relayIP = relay
|
|
||||||
return s.selectRole()
|
|
||||||
}
|
|
||||||
s.resetPingTimer()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateClientRelayed struct {
|
|
||||||
sharedKey [32]byte
|
|
||||||
*stateBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateClientRelayed(b *stateBase) peerState {
|
|
||||||
s := &stateClientRelayed{stateBase: b}
|
|
||||||
|
|
||||||
s.staged.dataCipher = newDataCipher()
|
|
||||||
s.sharedKey = s.staged.dataCipher.Key()
|
|
||||||
s.publish()
|
|
||||||
|
|
||||||
s.sendControlPacket(newPingPacket())
|
|
||||||
s.resetPingTimer()
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateClientRelayed) Name() string { return "client-relayed" }
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type stateServerRelayed struct {
|
|
||||||
*stateBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStateServerRelayed(b *stateBase) peerState {
|
|
||||||
s := &stateServerRelayed{b}
|
|
||||||
s.stopPingTimer()
|
|
||||||
s.resetTimeoutTimer()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateServerRelayed) Name() string { return "server-relayed" }
|
|
||||||
|
|
||||||
func (s *stateServerRelayed) OnTimeoutTimer() peerState {
|
|
||||||
return newStateSelectRelay(s.stateBase)
|
|
||||||
}
|
|
276
node/peer-super-states.go
Normal file
276
node/peer-super-states.go
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
"vppn/m"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) noPeer() stateFunc {
|
||||||
|
return s.peerUpdate(<-s.peerUpdates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) peerUpdate(peer *m.Peer) stateFunc {
|
||||||
|
return func() stateFunc { return s._peerUpdate(peer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *peerSuper) _peerUpdate(peer *m.Peer) stateFunc {
|
||||||
|
defer s.publish()
|
||||||
|
|
||||||
|
s.peer = peer
|
||||||
|
s.staged = peerRoutingData{}
|
||||||
|
|
||||||
|
if s.peer == nil {
|
||||||
|
return s.noPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
s.staged.controlCipher = newControlCipher(s.privKey, peer.EncPubKey)
|
||||||
|
s.staged.dataCipher = newDataCipher()
|
||||||
|
|
||||||
|
if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid {
|
||||||
|
s.remotePub = true
|
||||||
|
s.staged.relay = peer.Mediator
|
||||||
|
s.staged.remoteAddr = netip.AddrPortFrom(ip, peer.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.remotePub == s.localPub {
|
||||||
|
if s.localIP < s.remoteIP {
|
||||||
|
return s.serverAccept
|
||||||
|
}
|
||||||
|
return s.clientInit
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.remotePub {
|
||||||
|
return s.clientInit
|
||||||
|
}
|
||||||
|
return s.serverAccept
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) serverAccept() stateFunc {
|
||||||
|
s.logf("STATE: server-accept")
|
||||||
|
s.staged.up = false
|
||||||
|
s.staged.dataCipher = nil
|
||||||
|
s.staged.remoteAddr = zeroAddrPort
|
||||||
|
s.staged.relayIP = 0
|
||||||
|
s.publish()
|
||||||
|
|
||||||
|
var syn synPacket
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case peer := <-s.peerUpdates:
|
||||||
|
return s.peerUpdate(peer)
|
||||||
|
|
||||||
|
case pkt := <-s.controlPackets:
|
||||||
|
switch p := pkt.Payload.(type) {
|
||||||
|
|
||||||
|
case synPacket:
|
||||||
|
syn = p
|
||||||
|
s.staged.remoteAddr = pkt.RemoteAddr
|
||||||
|
s.staged.dataCipher = newDataCipherFromKey(syn.SharedKey)
|
||||||
|
s.staged.relayIP = syn.RelayIP
|
||||||
|
s.publish()
|
||||||
|
s.sendControlPacket(newSynAckPacket(p.TraceID))
|
||||||
|
|
||||||
|
case ackPacket:
|
||||||
|
if p.TraceID != syn.TraceID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish.
|
||||||
|
return s.serverConnected(syn.TraceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) serverConnected(traceID uint64) stateFunc {
|
||||||
|
s.logf("STATE: server-connected")
|
||||||
|
s.staged.up = true
|
||||||
|
s.publish()
|
||||||
|
return func() stateFunc {
|
||||||
|
return s._serverConnected(traceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *peerSuper) _serverConnected(traceID uint64) stateFunc {
|
||||||
|
|
||||||
|
timeoutTimer := time.NewTimer(timeoutInterval)
|
||||||
|
defer timeoutTimer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case peer := <-s.peerUpdates:
|
||||||
|
return s.peerUpdate(peer)
|
||||||
|
|
||||||
|
case pkt := <-s.controlPackets:
|
||||||
|
switch p := pkt.Payload.(type) {
|
||||||
|
|
||||||
|
case ackPacket:
|
||||||
|
if p.TraceID != traceID {
|
||||||
|
return s.serverAccept
|
||||||
|
}
|
||||||
|
|
||||||
|
s.sendControlPacket(ackPacket{TraceID: traceID})
|
||||||
|
timeoutTimer.Reset(timeoutInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-timeoutTimer.C:
|
||||||
|
s.logf("server timeout")
|
||||||
|
return s.serverAccept
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) clientInit() stateFunc {
|
||||||
|
s.logf("STATE: client-init")
|
||||||
|
if !s.remotePub {
|
||||||
|
// TODO: Check local discovery for IP.
|
||||||
|
// TODO: Attempt UDP hole punch.
|
||||||
|
// TODO: client-relayed
|
||||||
|
return s.clientSelectRelay
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.clientDial
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) clientSelectRelay() stateFunc {
|
||||||
|
s.logf("STATE: client-select-relay")
|
||||||
|
|
||||||
|
timer := time.NewTimer(0)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case peer := <-s.peerUpdates:
|
||||||
|
return s.peerUpdate(peer)
|
||||||
|
|
||||||
|
case <-timer.C:
|
||||||
|
ip := s.selectRelayIP()
|
||||||
|
if ip != 0 {
|
||||||
|
s.logf("Got relay: %d", ip)
|
||||||
|
s.staged.relayIP = ip
|
||||||
|
s.publish()
|
||||||
|
return s.clientDial
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logf("No relay available.")
|
||||||
|
timer.Reset(pingInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *peerSuper) selectRelayIP() byte {
|
||||||
|
possible := make([]byte, 0, 8)
|
||||||
|
for i, peer := range s.peers {
|
||||||
|
if peer.CanRelay() {
|
||||||
|
possible = append(possible, byte(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(possible) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return possible[rand.Intn(len(possible))]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) clientDial() stateFunc {
|
||||||
|
s.logf("STATE: client-dial")
|
||||||
|
|
||||||
|
var (
|
||||||
|
syn = synPacket{
|
||||||
|
TraceID: newTraceID(),
|
||||||
|
SharedKey: s.staged.dataCipher.Key(),
|
||||||
|
RelayIP: s.staged.relayIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = time.NewTimer(dialTimeout)
|
||||||
|
)
|
||||||
|
|
||||||
|
defer timeout.Stop()
|
||||||
|
|
||||||
|
s.sendControlPacket(syn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case peer := <-s.peerUpdates:
|
||||||
|
return s.peerUpdate(peer)
|
||||||
|
|
||||||
|
case pkt := <-s.controlPackets:
|
||||||
|
switch p := pkt.Payload.(type) {
|
||||||
|
case synAckPacket:
|
||||||
|
if p.TraceID != syn.TraceID {
|
||||||
|
continue // Hmm...
|
||||||
|
}
|
||||||
|
s.sendControlPacket(ackPacket{TraceID: syn.TraceID})
|
||||||
|
return s.clientConnected(syn.TraceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-timeout.C:
|
||||||
|
return s.clientInit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) clientConnected(traceID uint64) stateFunc {
|
||||||
|
s.logf("STATE: client-connected")
|
||||||
|
s.staged.up = true
|
||||||
|
s.publish()
|
||||||
|
|
||||||
|
return func() stateFunc {
|
||||||
|
return s._clientConnected(traceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *peerSuper) _clientConnected(traceID uint64) stateFunc {
|
||||||
|
|
||||||
|
pingTimer := time.NewTimer(pingInterval)
|
||||||
|
timeoutTimer := time.NewTimer(timeoutInterval)
|
||||||
|
|
||||||
|
defer pingTimer.Stop()
|
||||||
|
defer timeoutTimer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case peer := <-s.peerUpdates:
|
||||||
|
return s.peerUpdate(peer)
|
||||||
|
|
||||||
|
case pkt := <-s.controlPackets:
|
||||||
|
switch p := pkt.Payload.(type) {
|
||||||
|
|
||||||
|
case ackPacket:
|
||||||
|
if p.TraceID != traceID {
|
||||||
|
return s.clientInit
|
||||||
|
}
|
||||||
|
timeoutTimer.Reset(timeoutInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-pingTimer.C:
|
||||||
|
s.sendControlPacket(ackPacket{TraceID: traceID})
|
||||||
|
pingTimer.Reset(pingInterval)
|
||||||
|
|
||||||
|
case <-timeoutTimer.C:
|
||||||
|
s.logf("client timeout")
|
||||||
|
return s.clientInit
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
node/peer-super.go
Normal file
80
node/peer-super.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
|
"vppn/m"
|
||||||
|
)
|
||||||
|
|
||||||
|
type peerSuper struct {
|
||||||
|
// The purpose of this state machine is to manage this published data.
|
||||||
|
published *atomic.Pointer[peerRoutingData]
|
||||||
|
staged peerRoutingData // Local copy of shared data. See publish().
|
||||||
|
|
||||||
|
// The other remote peers.
|
||||||
|
peers *remotePeers
|
||||||
|
|
||||||
|
// Immutable data.
|
||||||
|
localIP byte
|
||||||
|
localPub bool
|
||||||
|
remoteIP byte
|
||||||
|
privKey []byte
|
||||||
|
conn *connWriter
|
||||||
|
|
||||||
|
// For sending to peer.
|
||||||
|
counter *uint64
|
||||||
|
|
||||||
|
// Mutable peer data.
|
||||||
|
peer *m.Peer
|
||||||
|
remotePub bool
|
||||||
|
|
||||||
|
// Incoming events.
|
||||||
|
peerUpdates chan *m.Peer
|
||||||
|
controlPackets chan controlPacket
|
||||||
|
|
||||||
|
// Buffers
|
||||||
|
buf []byte
|
||||||
|
encBuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateFunc func() stateFunc
|
||||||
|
|
||||||
|
func (s *peerSuper) Run() {
|
||||||
|
state := s.noPeer
|
||||||
|
for {
|
||||||
|
state = state()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) logf(msg string, args ...any) {
|
||||||
|
log.Printf(fmt.Sprintf("[%03d] ", s.remoteIP)+msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) publish() {
|
||||||
|
data := s.staged
|
||||||
|
s.published.Store(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (s *peerSuper) sendControlPacket(pkt interface{ Marshal([]byte) []byte }) {
|
||||||
|
buf := pkt.Marshal(s.buf)
|
||||||
|
h := header{
|
||||||
|
StreamID: controlStreamID,
|
||||||
|
Counter: atomic.AddUint64(s.counter, 1),
|
||||||
|
SourceIP: s.localIP,
|
||||||
|
DestIP: s.remoteIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = s.staged.controlCipher.Encrypt(h, buf, s.encBuf)
|
||||||
|
if s.staged.relayIP != 0 {
|
||||||
|
s.peers[s.staged.relayIP].RelayTo(s.remoteIP, buf)
|
||||||
|
} else {
|
||||||
|
s.conn.WriteTo(buf, s.staged.remoteAddr)
|
||||||
|
}
|
||||||
|
}
|
@ -15,55 +15,20 @@ const (
|
|||||||
func (rp *remotePeer) supervise(conf m.PeerConfig) {
|
func (rp *remotePeer) supervise(conf m.PeerConfig) {
|
||||||
defer panicHandler()
|
defer panicHandler()
|
||||||
|
|
||||||
base := &stateBase{
|
super := &peerSuper{
|
||||||
published: rp.published,
|
published: rp.published,
|
||||||
peers: rp.peers,
|
peers: rp.peers,
|
||||||
localIP: rp.localIP,
|
localIP: rp.localIP,
|
||||||
|
localPub: addrIsValid(conf.PublicIP),
|
||||||
remoteIP: rp.remoteIP,
|
remoteIP: rp.remoteIP,
|
||||||
privKey: conf.EncPrivKey,
|
privKey: conf.EncPrivKey,
|
||||||
localPub: addrIsValid(conf.PublicIP),
|
|
||||||
conn: rp.conn,
|
conn: rp.conn,
|
||||||
counter: &rp.counter,
|
counter: &rp.counter,
|
||||||
pingTimer: time.NewTimer(time.Second),
|
peerUpdates: rp.peerUpdates,
|
||||||
timeoutTimer: time.NewTimer(time.Second),
|
controlPackets: rp.controlPackets,
|
||||||
buf: make([]byte, bufferSize),
|
buf: make([]byte, bufferSize),
|
||||||
encBuf: make([]byte, bufferSize),
|
encBuf: make([]byte, bufferSize),
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
go super.Run()
|
||||||
curState peerState = newStateNoPeer(base)
|
|
||||||
nextState peerState
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
nextState = nil
|
|
||||||
|
|
||||||
select {
|
|
||||||
case peer := <-rp.peerUpdates:
|
|
||||||
nextState = curState.OnPeerUpdate(peer)
|
|
||||||
|
|
||||||
case pkt := <-rp.controlPackets:
|
|
||||||
switch p := pkt.Payload.(type) {
|
|
||||||
case synPacket:
|
|
||||||
nextState = curState.OnSyn(pkt.RemoteAddr, p)
|
|
||||||
case synAckPacket:
|
|
||||||
nextState = curState.OnSynAck(pkt.RemoteAddr, p)
|
|
||||||
case ackPacket:
|
|
||||||
nextState = curState.OnAck(pkt.RemoteAddr, p)
|
|
||||||
default:
|
|
||||||
// Unknown packet type.
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-base.pingTimer.C:
|
|
||||||
nextState = curState.OnPingTimer()
|
|
||||||
|
|
||||||
case <-base.timeoutTimer.C:
|
|
||||||
nextState = curState.OnTimeoutTimer()
|
|
||||||
}
|
|
||||||
|
|
||||||
if nextState != nil {
|
|
||||||
rp.logf("%s --> %s", curState.Name(), nextState.Name())
|
|
||||||
curState = nextState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
13
node/peer.go
13
node/peer.go
@ -41,6 +41,10 @@ type remotePeer struct {
|
|||||||
// Used for sending control and data packets. Atomic access only.
|
// Used for sending control and data packets. Atomic access only.
|
||||||
counter uint64
|
counter uint64
|
||||||
|
|
||||||
|
// Only accessed in HandlePeerUpdate. Used to determine if we should send
|
||||||
|
// the peer update to the peerSuper.
|
||||||
|
peerVersion int64
|
||||||
|
|
||||||
// For communicating with the supervisor thread.
|
// For communicating with the supervisor thread.
|
||||||
peerUpdates chan *m.Peer
|
peerUpdates chan *m.Peer
|
||||||
controlPackets chan controlPacket
|
controlPackets chan controlPacket
|
||||||
@ -75,7 +79,12 @@ func (rp *remotePeer) logf(msg string, args ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rp *remotePeer) HandlePeerUpdate(peer *m.Peer) {
|
func (rp *remotePeer) HandlePeerUpdate(peer *m.Peer) {
|
||||||
|
if peer == nil {
|
||||||
rp.peerUpdates <- peer
|
rp.peerUpdates <- peer
|
||||||
|
} else if peer.Version != rp.peerVersion {
|
||||||
|
rp.peerVersion = peer.Version
|
||||||
|
rp.peerUpdates <- peer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -209,7 +218,7 @@ func (rp *remotePeer) HandleInterfacePacket(data []byte) {
|
|||||||
enc := routingData.dataCipher.Encrypt(h, data, rp.encryptBuf)
|
enc := routingData.dataCipher.Encrypt(h, data, rp.encryptBuf)
|
||||||
|
|
||||||
if routingData.relayIP != 0 {
|
if routingData.relayIP != 0 {
|
||||||
rp.peers[routingData.relayIP].RelayFor(rp.remoteIP, enc)
|
rp.peers[routingData.relayIP].RelayTo(rp.remoteIP, enc)
|
||||||
} else {
|
} else {
|
||||||
rp.SendData(data)
|
rp.SendData(data)
|
||||||
}
|
}
|
||||||
@ -224,7 +233,7 @@ func (rp *remotePeer) CanRelay() bool {
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
func (rp *remotePeer) RelayFor(destIP byte, data []byte) {
|
func (rp *remotePeer) RelayTo(destIP byte, data []byte) {
|
||||||
rp.encryptAndSend(relayStreamID, destIP, data)
|
rp.encryptAndSend(relayStreamID, destIP, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
var zeroAddrPort = netip.AddrPort{}
|
|
Loading…
x
Reference in New Issue
Block a user