diff --git a/cmd/hub/hub b/cmd/hub/hub new file mode 100755 index 0000000..70a1361 Binary files /dev/null and b/cmd/hub/hub differ diff --git a/cmd/vppn/vppn b/cmd/vppn/vppn new file mode 100755 index 0000000..90bf1c8 Binary files /dev/null and b/cmd/vppn/vppn differ diff --git a/peer/bufferset.go b/peer/bufferset.go new file mode 100644 index 0000000..333deff --- /dev/null +++ b/peer/bufferset.go @@ -0,0 +1 @@ +package peer diff --git a/peer/cipher.go b/peer/cipher.go new file mode 100644 index 0000000..333deff --- /dev/null +++ b/peer/cipher.go @@ -0,0 +1 @@ +package peer diff --git a/peer/globals.go b/peer/globals.go index 6a5bb0b..4651bc4 100644 --- a/peer/globals.go +++ b/peer/globals.go @@ -31,6 +31,7 @@ var multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom( netip.AddrFrom4([4]byte{224, 0, 0, 157}), 4560)) +<<<<<<< HEAD // ---------------------------------------------------------------------------- type Globals struct { @@ -105,5 +106,73 @@ func NewGlobals( g.RemotePeers[i].Store(newRemote(g, byte(i))) } + return g +======= +type marshaller interface { + Marshal([]byte) []byte +>>>>>>> 69f2536 (WIP) +} + +// ---------------------------------------------------------------------------- + +type Globals struct { + LocalConfig // Embed, immutable. + + // Local public address (if available). Immutable. + LocalAddr netip.AddrPort + + // True if local public address is valid. Immutable. + LocalAddrValid bool + + // All remote peers by VPN IP. + RemotePeers [256]*atomic.Pointer[Remote] + + // Discovered public addresses. + PubAddrs *pubAddrStore + + // Attempts to ensure that we have a relay available. + RelayHandler *relayHandler + + // Send UDP - Global function to write UDP packets. + SendUDP func(b []byte, addr netip.AddrPort) (n int, err error) + + // Global TUN interface. + IFace io.ReadWriteCloser +} + +func NewGlobals( + localConfig LocalConfig, + localAddr netip.AddrPort, + conn *net.UDPConn, + iface io.ReadWriteCloser, +) (g Globals) { + g.LocalConfig = localConfig + + g.LocalAddr = localAddr + g.LocalAddrValid = localAddr.IsValid() + + g.PubAddrs = newPubAddrStore(localAddr) + + g.RelayHandler = newRelayHandler() + + // Use a lock here avoids starvation, at least on my Linux machine. + sendLock := sync.Mutex{} + g.SendUDP = func(b []byte, addr netip.AddrPort) (int, error) { + sendLock.Lock() + n, err := conn.WriteToUDPAddrPort(b, addr) + sendLock.Unlock() + return n, err + } + + g.IFace = iface + + for i := range g.RemotePeers { + g.RemotePeers[i] = &atomic.Pointer[Remote]{} + } + + for i := range g.RemotePeers { + g.RemotePeers[i].Store(newRemote(g, byte(i))) + } + return g } diff --git a/peer/mcreader.go b/peer/mcreader.go index e29bab6..5bbfe71 100644 --- a/peer/mcreader.go +++ b/peer/mcreader.go @@ -41,6 +41,7 @@ func runMCReaderInner(g Globals) { logf("Failed to open discovery packet?") continue } + log.Printf("Got local discovery from %v: %v", remoteAddr, h) g.RemotePeers[h.SourceIP].Load().HandleLocalDiscoveryPacket(h, remoteAddr, buf) } diff --git a/peer/peer.go b/peer/peer.go index 69adbaf..ff95e68 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -11,7 +11,10 @@ import ( "net/http" "net/netip" "net/url" +<<<<<<< HEAD "os" +======= +>>>>>>> 69f2536 (WIP) "vppn/m" ) @@ -50,6 +53,7 @@ func newPeerMain(args mainArgs) *peerMain { log.Fatalf("Failed to load network state: %v", err) } +<<<<<<< HEAD startupCount, err := loadStartupCount(args.NetName) if err != nil { if !os.IsNotExist(err) { @@ -66,6 +70,8 @@ func newPeerMain(args mainArgs) *peerMain { log.Fatalf("Failed to write startup count: %v", err) } +======= +>>>>>>> 69f2536 (WIP) iface, err := openInterface(config.Network, config.LocalPeerIP, args.NetName) if err != nil { log.Fatalf("Failed to open interface: %v", err) @@ -73,6 +79,7 @@ func newPeerMain(args mainArgs) *peerMain { localPeer := state.Peers[config.LocalPeerIP] + log.Printf("XXXXX %v %v", config.LocalPeerIP, localPeer) myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", localPeer.Port)) if err != nil { log.Fatalf("Failed to resolve UDP address: %v", err) @@ -93,7 +100,11 @@ func newPeerMain(args mainArgs) *peerMain { localAddr = netip.AddrPortFrom(ip, localPeer.Port) } +<<<<<<< HEAD g := NewGlobals(config, startupCount, localAddr, conn, iface) +======= + g := NewGlobals(config, localAddr, conn, iface) +>>>>>>> 69f2536 (WIP) hubPoller, err := NewHubPoller(g, args.NetName, args.HubAddress, args.APIKey) if err != nil { diff --git a/peer/peer_test.go b/peer/peer_test.go new file mode 100644 index 0000000..863ca8f --- /dev/null +++ b/peer/peer_test.go @@ -0,0 +1,114 @@ +package peer + +import ( + "bytes" + "crypto/rand" + mrand "math/rand" + "net/netip" + "sync/atomic" +) + +// A test peer. +type P struct { + cryptoKeys + RT *atomic.Pointer[routingTable] + Conn *TestUDPConn + IFace *TestIFace + ConnReader *ConnReader + IFReader *IFReader +} + +func NewPeerForTesting(n *TestNetwork, ip byte, addr netip.AddrPort) P { + p := P{ + cryptoKeys: generateKeys(), + RT: &atomic.Pointer[routingTable]{}, + IFace: NewTestIFace(), + } + + rt := newRoutingTable(ip, addr) + p.RT.Store(&rt) + p.Conn = n.NewUDPConn(addr) + //p.ConnWriter = NewConnWriter(p.Conn.WriteToUDPAddrPort, p.RT) + + return p +} + +func ConnectPeers(p1, p2 *P) { + rt1 := p1.RT.Load() + rt2 := p2.RT.Load() + + ip1 := rt1.LocalIP + ip2 := rt2.LocalIP + + rt1.Peers[ip2].Up = true + rt1.Peers[ip2].Direct = true + rt1.Peers[ip2].Relay = true + rt1.Peers[ip2].DirectAddr = rt2.LocalAddr + rt1.Peers[ip2].PubSignKey = p2.PubSignKey + rt1.Peers[ip2].ControlCipher = newControlCipher(p1.PrivKey, p2.PubKey) + rt1.Peers[ip2].DataCipher = newDataCipher() + + rt2.Peers[ip1].Up = true + rt2.Peers[ip1].Direct = true + rt2.Peers[ip1].Relay = true + rt2.Peers[ip1].DirectAddr = rt1.LocalAddr + rt2.Peers[ip1].PubSignKey = p1.PubSignKey + rt2.Peers[ip1].ControlCipher = newControlCipher(p2.PrivKey, p1.PubKey) + rt2.Peers[ip1].DataCipher = rt1.Peers[ip2].DataCipher +} + +func NewPeersForTesting() (p1, p2, p3 P) { + n := NewTestNetwork() + + p1 = NewPeerForTesting( + n, + 1, + netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), 100)) + + p2 = NewPeerForTesting( + n, + 2, + netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 2}), 200)) + + p3 = NewPeerForTesting( + n, + 3, + netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 3}), 300)) + + ConnectPeers(&p1, &p2) + ConnectPeers(&p1, &p3) + ConnectPeers(&p2, &p3) + + return +} + +func RandPacket() []byte { + n := mrand.Intn(1200) + b := make([]byte, n) + rand.Read(b) + return b +} + +func ModifyPacket(in []byte) []byte { + x := make([]byte, 1) + + for { + rand.Read(x) + out := bytes.Clone(in) + idx := mrand.Intn(len(out)) + if out[idx] != x[0] { + out[idx] = x[0] + return out + } + } +} + +// ---------------------------------------------------------------------------- + +type UnknownControlPacket struct { + TraceID uint64 +} + +func (p UnknownControlPacket) Marshal(buf []byte) []byte { + return newBinWriter(buf).Byte(255).Uint64(p.TraceID).Build() +} diff --git a/peer/peerfsm.dot b/peer/peerfsm.dot new file mode 100644 index 0000000..fa7f461 --- /dev/null +++ b/peer/peerfsm.dot @@ -0,0 +1,28 @@ +digraph d { + disconnected -> peerUpdating; + peerUpdating -> disconnected; + peerUpdating -> server; + peerUpdating -> clientInit; + server -> peerUpdating; + clientInit -> peerUpdating; + clientInit -> clientInit; + clientInit -> client; + client -> clientInit; + client -> peerUpdating; + + clientInitializing -> clientSyncing; + clientSyncing -> clientInitializing; + clientSyncing -> clientUpIndirect; + clientSyncing -> clientUpDirect; + clientUpIndirect -> clientUpDirect; + clientUpIndirect -> clientInitializing; + clientUpDirect -> clientInitializing; + + serverInitializing -> serverSyncing; + serverSyncing -> serverInitializing; + serverSyncing -> serverUpIndirect; + serverSyncing -> serverUpDirect; + serverUpIndirect -> serverUpDirect; + serverUpIndirect -> serverInitializing; + serverUpDirect -> serverInitializing; +} \ No newline at end of file diff --git a/peer/peersuper.go b/peer/peersuper.go new file mode 100644 index 0000000..333deff --- /dev/null +++ b/peer/peersuper.go @@ -0,0 +1 @@ +package peer diff --git a/peer/relay.go b/peer/relay.go new file mode 100644 index 0000000..333deff --- /dev/null +++ b/peer/relay.go @@ -0,0 +1 @@ +package peer diff --git a/peer/remote.go b/peer/remote.go index 84b5c16..05c3af0 100644 --- a/peer/remote.go +++ b/peer/remote.go @@ -6,7 +6,14 @@ import ( "net/netip" "strings" "sync/atomic" +<<<<<<< HEAD "vppn/m" +======= + "time" + "vppn/m" + + "git.crumpington.com/lib/go/ratelimiter" +>>>>>>> 69f2536 (WIP) ) // ---------------------------------------------------------------------------- @@ -37,6 +44,10 @@ type Remote struct { Globals RemotePeerIP byte // Immutable. +<<<<<<< HEAD +======= + limiter *ratelimiter.Limiter +>>>>>>> 69f2536 (WIP) dupCheck *dupCheck sendCounter uint64 // init to startupCount << 48. Atomic access only. @@ -49,9 +60,19 @@ func newRemote(g Globals, remotePeerIP byte) *Remote { r := &Remote{ Globals: g, RemotePeerIP: remotePeerIP, +<<<<<<< HEAD dupCheck: newDupCheck(0), sendCounter: (uint64(g.StartupCount) << 48) + 1, messages: make(chan any, 8), +======= + limiter: ratelimiter.New(ratelimiter.Config{ + FillPeriod: 20 * time.Millisecond, + MaxWaitCount: 1, + }), + dupCheck: newDupCheck(0), + sendCounter: uint64(time.Now().Unix()<<30) + 1, + messages: make(chan any, 8), +>>>>>>> 69f2536 (WIP) } r.config.Store(&remoteConfig{}) return r @@ -79,19 +100,36 @@ func (r *Remote) updateConf(conf remoteConfig) { // ---------------------------------------------------------------------------- func (r *Remote) sendUDP(b []byte, addr netip.AddrPort) { +<<<<<<< HEAD if _, err := r.SendUDP(b, addr); err != nil { r.logf("Failed to send UDP packet: %v", err) +======= + if err := r.limiter.Limit(); err != nil { + r.logf("Rate limiter") + return + } + if _, err := r.SendUDP(b, addr); err != nil { + r.logf("Failed to send URP packet: %v", err) +>>>>>>> 69f2536 (WIP) } } // ---------------------------------------------------------------------------- +<<<<<<< HEAD func (r *Remote) encryptData(conf remoteConfig, destIP byte, packet []byte) []byte { +======= +func (r *Remote) encryptData(conf remoteConfig, packet []byte) []byte { +>>>>>>> 69f2536 (WIP) h := Header{ StreamID: dataStreamID, Counter: atomic.AddUint64(&r.sendCounter, 1), SourceIP: r.Globals.LocalPeerIP, +<<<<<<< HEAD DestIP: destIP, +======= + DestIP: r.RemotePeerIP, +>>>>>>> 69f2536 (WIP) } return conf.DataCipher.Encrypt(h, packet, packet[len(packet):cap(packet)]) } @@ -116,6 +154,7 @@ func (r *Remote) SendDataTo(data []byte) { return } +<<<<<<< HEAD // Direct: if conf.Direct { @@ -124,6 +163,17 @@ func (r *Remote) SendDataTo(data []byte) { } // Relayed: +======= + if conf.Direct { + r.sendDataDirect(conf, data) + } else { + r.sendDataRelayed(conf, data) + } +} + +// sendDataRelayed sends data to the remote via the relay. +func (r *Remote) sendDataRelayed(conf remoteConfig, data []byte) { +>>>>>>> 69f2536 (WIP) relay := r.RelayHandler.Load() if relay == nil { @@ -131,15 +181,29 @@ func (r *Remote) SendDataTo(data []byte) { return } +<<<<<<< HEAD relay.relayData(conf.Peer.PeerIP, r.encryptData(conf, conf.Peer.PeerIP, data)) } func (r *Remote) relayData(toIP byte, enc []byte) { +======= + relay.relayData(r.encryptData(conf, data)) +} + +// sendDataDirect sends data to the remote directly. +func (r *Remote) sendDataDirect(conf remoteConfig, data []byte) { + r.logf("Sending data direct...") + r.sendUDP(r.encryptData(conf, data), conf.DirectAddr) +} + +func (r *Remote) relayData(enc []byte) { +>>>>>>> 69f2536 (WIP) conf := r.conf() if !conf.Up || !conf.Direct { r.logf("Cannot relay: not up or not a direct connection") return } +<<<<<<< HEAD r.sendUDP(r.encryptData(conf, toIP, enc), conf.DirectAddr) } @@ -154,6 +218,32 @@ func (r *Remote) sendControl(conf remoteConfig, data []byte) { // Relayed: +======= + r.sendDataDirect(conf, enc) +} + +func (r *Remote) sendControl(conf remoteConfig, data []byte) { + if conf.Direct { + r.sendControlDirect(conf, data) + } else { + r.sendControlRelayed(conf, data) + } +} + +func (r *Remote) sendControlToAddr(buf []byte, addr netip.AddrPort) { + enc := r.encryptControl(r.conf(), buf) + r.sendUDP(enc, addr) +} + +func (r *Remote) sendControlDirect(conf remoteConfig, data []byte) { + r.logf("Sending control direct...") + enc := r.encryptControl(conf, data) + r.sendUDP(enc, conf.DirectAddr) +} + +func (r *Remote) sendControlRelayed(conf remoteConfig, data []byte) { + r.logf("Sending control relayed...") +>>>>>>> 69f2536 (WIP) relay := r.RelayHandler.Load() if relay == nil { @@ -161,12 +251,16 @@ func (r *Remote) sendControl(conf remoteConfig, data []byte) { return } +<<<<<<< HEAD relay.relayData(conf.Peer.PeerIP, r.encryptControl(conf, data)) } func (r *Remote) sendControlToAddr(buf []byte, addr netip.AddrPort) { enc := r.encryptControl(r.conf(), buf) r.sendUDP(enc, addr) +======= + relay.relayData(r.encryptControl(conf, data)) +>>>>>>> 69f2536 (WIP) } func (r *Remote) forwardPacket(data []byte) { @@ -244,9 +338,13 @@ func (r *Remote) handleDataPacket(h Header, data []byte) { // For local. if h.DestIP == r.LocalPeerIP { if _, err := r.IFace.Write(dec); err != nil { +<<<<<<< HEAD // This could be a malformed packet from a peer, so we don't crash if it // happens. r.logf("Failed to write to interface: %v", err) +======= + log.Fatalf("Failed to write to interface: %v", err) +>>>>>>> 69f2536 (WIP) } return } @@ -275,6 +373,10 @@ func (r *Remote) HandleLocalDiscoveryPacket(h Header, srcAddr netip.AddrPort, da SrcIP: h.SourceIP, SrcAddr: srcAddr, } +<<<<<<< HEAD +======= + r.logf("Got local discovery packet from %v.", srcAddr) +>>>>>>> 69f2536 (WIP) select { case r.messages <- msg: diff --git a/peer/remotefsm.go b/peer/remotefsm.go index 7a68c84..354a00e 100644 --- a/peer/remotefsm.go +++ b/peer/remotefsm.go @@ -9,11 +9,14 @@ import ( type stateFunc func(msg any) stateFunc +<<<<<<< HEAD type sentProbe struct { SentAt time.Time Addr netip.AddrPort } +======= +>>>>>>> 69f2536 (WIP) type remoteFSM struct { *Remote @@ -161,13 +164,23 @@ func (r *remoteFSM) stateServer_onInit(msg controlMsg[packetInit]) { } func (r *remoteFSM) stateServer_onSyn(msg controlMsg[packetSyn]) { +<<<<<<< HEAD +======= + r.logf("Got SYN: %v", msg.Packet) +>>>>>>> 69f2536 (WIP) r.lastSeen = time.Now() p := msg.Packet // Before we can respond to this packet, we need to make sure the // route is setup properly. conf := r.conf() +<<<<<<< HEAD logSyn := !conf.Up || conf.Direct != p.Direct +======= + if !conf.Up || conf.Direct != p.Direct { + r.logf("Got SYN.") + } +>>>>>>> 69f2536 (WIP) conf.Up = true conf.Direct = p.Direct @@ -181,10 +194,13 @@ func (r *remoteFSM) stateServer_onSyn(msg controlMsg[packetSyn]) { r.updateConf(conf) +<<<<<<< HEAD if logSyn { r.logf("Got SYN.") } +======= +>>>>>>> 69f2536 (WIP) r.sendControl(conf, packetAck{ TraceID: p.TraceID, ToAddr: conf.DirectAddr, @@ -201,7 +217,11 @@ func (r *remoteFSM) stateServer_onSyn(msg controlMsg[packetSyn]) { break } r.logf("Probing %v...", addr) +<<<<<<< HEAD r.sendControlToAddr(packetProbe{TraceID: r.NewTraceID()}.Marshal(r.buf), addr) +======= + r.sendControlToAddr(packetProbe{TraceID: newTraceID()}.Marshal(r.buf), addr) +>>>>>>> 69f2536 (WIP) } } @@ -269,7 +289,11 @@ func (r *remoteFSM) stateClientInit(iMsg any) stateFunc { func (r *remoteFSM) stateClientInit_sendInit() { conf := r.conf() +<<<<<<< HEAD r.traceID = r.NewTraceID() +======= + r.traceID = newTraceID() +>>>>>>> 69f2536 (WIP) init := packetInit{ TraceID: r.traceID, Direct: conf.Direct, @@ -316,7 +340,11 @@ func (r *remoteFSM) enterClient() stateFunc { conf := r.conf() r.probes = make(map[uint64]sentProbe, 8) +<<<<<<< HEAD r.traceID = r.NewTraceID() +======= + r.traceID = newTraceID() +>>>>>>> 69f2536 (WIP) r.stateClient_sendSyn(conf) r.pingTimer.Reset(pingInterval) @@ -382,7 +410,11 @@ func (r *remoteFSM) stateClient_cleanProbes() { } func (r *remoteFSM) stateClient_sendProbeTo(addr netip.AddrPort) { +<<<<<<< HEAD probe := packetProbe{TraceID: r.NewTraceID()} +======= + probe := packetProbe{TraceID: newTraceID()} +>>>>>>> 69f2536 (WIP) r.probes[probe.TraceID] = sentProbe{ SentAt: time.Now(), Addr: addr, @@ -408,7 +440,11 @@ func (r *remoteFSM) stateClient_onProbe(msg controlMsg[packetProbe]) { conf.DirectAddr = sent.Addr r.updateConf(conf) +<<<<<<< HEAD r.traceID = r.NewTraceID() +======= + r.traceID = newTraceID() +>>>>>>> 69f2536 (WIP) r.stateClient_sendSyn(conf) r.logf("Successful probe to %v.", sent.Addr) } @@ -435,6 +471,10 @@ func (r *remoteFSM) stateClient_onPingTimer() stateFunc { return r.enterClientInit() } +<<<<<<< HEAD +======= + r.traceID = newTraceID() +>>>>>>> 69f2536 (WIP) r.stateClient_sendSyn(conf) return r.stateClient } diff --git a/peer/remotestate-disconnected.go b/peer/remotestate-disconnected.go new file mode 100644 index 0000000..333deff --- /dev/null +++ b/peer/remotestate-disconnected.go @@ -0,0 +1 @@ +package peer diff --git a/peer/routingtable.go b/peer/routingtable.go new file mode 100644 index 0000000..d29d4b0 --- /dev/null +++ b/peer/routingtable.go @@ -0,0 +1,138 @@ +package peer + +import ( + "net/netip" + "sync/atomic" + "time" +) + +// TODO: Remove +func newRemotePeer(ip byte) *remotePeer { + counter := uint64(time.Now().Unix()<<30 + 1) + return &remotePeer{ + IP: ip, + counter: &counter, + dupCheck: newDupCheck(0), + } +} + +// ---------------------------------------------------------------------------- + +type remotePeer struct { + localIP byte + IP byte // VPN IP of peer (last byte). + Up bool // True if data can be sent on the peer. + Relay bool // True if the peer is a relay. + Direct bool // True if this is a direct connection. + DirectAddr netip.AddrPort // Remote address if directly connected. + PubSignKey []byte + ControlCipher *controlCipher + DataCipher *dataCipher + + counter *uint64 // For sending to. Atomic access only. + dupCheck *dupCheck // For receiving from. Not safe for concurrent use. +} + +func (p remotePeer) EncryptDataPacket(destIP byte, data, out []byte) []byte { + h := Header{ + StreamID: dataStreamID, + Counter: atomic.AddUint64(p.counter, 1), + SourceIP: p.localIP, + DestIP: destIP, + } + return p.DataCipher.Encrypt(h, data, out) +} + +// Decrypts and de-dups incoming data packets. +func (p remotePeer) DecryptDataPacket(h Header, enc, out []byte) ([]byte, error) { + dec, ok := p.DataCipher.Decrypt(enc, out) + if !ok { + return nil, errDecryptionFailed + } + + if p.dupCheck.IsDup(h.Counter) { + return nil, errDuplicateSeqNum + } + + return dec, nil +} + +// Peer must have a ControlCipher. +func (p remotePeer) EncryptControlPacket(pkt marshaller, tmp, out []byte) []byte { + tmp = pkt.Marshal(tmp) + h := Header{ + StreamID: controlStreamID, + Counter: atomic.AddUint64(p.counter, 1), + SourceIP: p.localIP, + DestIP: p.IP, + } + + return p.ControlCipher.Encrypt(h, tmp, out) +} + +// Returns a controlMsg[PacketType]. Peer must have a non-nil ControlCipher. +// +// This function also drops packets with duplicate sequence numbers. +func (p remotePeer) DecryptControlPacket(fromAddr netip.AddrPort, h Header, enc, tmp []byte) (any, error) { + out, ok := p.ControlCipher.Decrypt(enc, tmp) + if !ok { + return nil, errDecryptionFailed + } + + if p.dupCheck.IsDup(h.Counter) { + return nil, errDuplicateSeqNum + } + + msg, err := parseControlMsg(h.SourceIP, fromAddr, out) + if err != nil { + return nil, err + } + + return msg, nil +} + +// ---------------------------------------------------------------------------- + +type routingTable struct { + // The LocalIP is the configured IP address of the local peer on the VPN. + // + // This value is constant. + LocalIP byte + + // The LocalAddr is the configured local public address of the peer on the + // internet. If LocalAddr.IsValid(), then the local peer has a public + // address. + // + // This value is constant. + LocalAddr netip.AddrPort + + // The remote peer configurations. These are updated by + Peers [256]remotePeer + + // The current relay's VPN IP address, or zero if no relay is available. + RelayIP byte +} + +func newRoutingTable(localIP byte, localAddr netip.AddrPort) routingTable { + rt := routingTable{ + LocalIP: localIP, + LocalAddr: localAddr, + } + + for i := range rt.Peers { + counter := uint64(time.Now().Unix()<<30 + 1) + rt.Peers[i] = remotePeer{ + localIP: localIP, + IP: byte(i), + counter: &counter, + dupCheck: newDupCheck(0), + } + } + + return rt +} + +func (rt *routingTable) GetRelay() (remotePeer, bool) { + relay := rt.Peers[rt.RelayIP] + return relay, relay.Up && relay.Direct +}