Cleanup
This commit is contained in:
parent
c69b52abf0
commit
ee4f5e012c
@ -3,6 +3,9 @@
|
||||
## Roadmap
|
||||
|
||||
* Node: use symmetric encryption after handshake
|
||||
* AEAD-AES uses a 12 byte nonce. We need to shrink the header:
|
||||
* Remove Forward and replace it with a HeaderFlags bitfield.
|
||||
* Forward, Asym/Sym, ...
|
||||
* Use default port 456
|
||||
* Remove signing key from hub
|
||||
* Peer: UDP hole-punching
|
||||
|
@ -1,9 +0,0 @@
|
||||
digraph d {
|
||||
init -> null;
|
||||
init -> unconnectedServer;
|
||||
init -> unconnectedClient;
|
||||
init -> unconnectedMediated;
|
||||
unconnectedServer -> connectedServer;
|
||||
unconnectedClient -> connectedClient;
|
||||
unconnectedMediated -> connectedMediated;
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
const (
|
||||
pingInterval = time.Second * 8
|
||||
timeoutInterval = 32 * time.Second
|
||||
)
|
||||
|
||||
type connData struct {
|
||||
// Shared data.
|
||||
routes [MAX_IP]*atomic.Pointer[route]
|
||||
route *atomic.Pointer[route]
|
||||
|
||||
// Peer data.
|
||||
server bool // Never changes.
|
||||
peerIP byte // Never changes.
|
||||
encPrivKey []byte // Never changes.
|
||||
|
||||
peer *m.Peer // From hub.
|
||||
encSharedKey []byte // From hub + private key.
|
||||
publicAddr netip.AddrPort // From hub.
|
||||
|
||||
// Connection establishment and maintenance.
|
||||
pingTimer *time.Timer
|
||||
timeoutTimer *time.Timer
|
||||
|
||||
// Routing data.
|
||||
addr netip.AddrPort
|
||||
useMediator bool
|
||||
up bool
|
||||
|
||||
// For sending.
|
||||
buf []byte
|
||||
sender *safeConnSender
|
||||
}
|
||||
|
||||
func (d *connData) Route() *route {
|
||||
return &route{
|
||||
PeerIP: d.peerIP,
|
||||
Up: d.up,
|
||||
Mediator: d.peer.Mediator,
|
||||
EncSharedKey: d.encSharedKey,
|
||||
Addr: d.addr,
|
||||
useMediator: d.useMediator,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *connData) HandlePeerUpdate(state connState, update peerUpdate) connState {
|
||||
if d.peer != nil && update.Peer != nil && d.peer.Version == update.Peer.Version {
|
||||
return state
|
||||
}
|
||||
if d.peer == nil && update.Peer == nil {
|
||||
return state
|
||||
}
|
||||
return newStateFromPeerUpdate(update, d)
|
||||
}
|
||||
|
||||
func (d *connData) HandleSendPing() {
|
||||
route := d.route.Load()
|
||||
req := Ping{SentAt: time.Now().UnixMilli()}
|
||||
d.buf = req.Marshal(d.buf)
|
||||
d.sender.send(PACKET_TYPE_PING, d.buf, route, nil)
|
||||
d.pingTimer.Reset(pingInterval)
|
||||
}
|
||||
|
||||
func (d *connData) sendPong(w wrapper) {
|
||||
ping := w.Packet.(*Ping)
|
||||
route := d.route.Load()
|
||||
pong := Pong{
|
||||
SentAt: ping.SentAt,
|
||||
RecvdAt: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
d.buf = pong.Marshal(d.buf)
|
||||
d.sender.send(PACKET_TYPE_PONG, d.buf, route, nil)
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type connHandler struct {
|
||||
// Communication.
|
||||
mediatorUpdates chan byte
|
||||
peerUpdates chan peerUpdate
|
||||
packets chan wrapper
|
||||
|
||||
data *connData
|
||||
}
|
||||
|
||||
func newConnHandler(
|
||||
server bool,
|
||||
peerIP byte,
|
||||
routes [MAX_IP]*atomic.Pointer[route],
|
||||
encPrivKey []byte,
|
||||
sender *safeConnSender,
|
||||
) *connHandler {
|
||||
d := &connData{
|
||||
server: server,
|
||||
pingTimer: time.NewTimer(pingInterval),
|
||||
timeoutTimer: time.NewTimer(timeoutInterval),
|
||||
routes: routes,
|
||||
route: routes[peerIP],
|
||||
peerIP: peerIP,
|
||||
encPrivKey: encPrivKey,
|
||||
buf: make([]byte, BUFFER_SIZE),
|
||||
sender: sender,
|
||||
}
|
||||
|
||||
h := &connHandler{
|
||||
mediatorUpdates: make(chan byte, 1),
|
||||
peerUpdates: make(chan peerUpdate, 1),
|
||||
packets: make(chan wrapper, 1),
|
||||
data: d,
|
||||
}
|
||||
|
||||
go h.mainLoop()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *connHandler) mainLoop() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
data = h.data
|
||||
state = newConnNull(data)
|
||||
name = state.Name()
|
||||
)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case update := <-h.peerUpdates:
|
||||
state = data.HandlePeerUpdate(state, update)
|
||||
|
||||
case <-data.pingTimer.C:
|
||||
data.HandleSendPing()
|
||||
|
||||
case w := <-h.packets:
|
||||
state = state.HandlePacket(w)
|
||||
|
||||
case <-data.timeoutTimer.C:
|
||||
log.Printf("[%s] Connection timeout.", state.Name())
|
||||
state = state.HandleTimeout()
|
||||
}
|
||||
|
||||
if state.Name() != name {
|
||||
log.Printf("[%03d] STATE: %s", data.peerIP, state.Name())
|
||||
name = state.Name()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connHandler) HandlePacket(w wrapper) {
|
||||
select {
|
||||
case c.packets <- w:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connHandler) UpdatePeer(update peerUpdate) {
|
||||
select {
|
||||
case c.peerUpdates <- update:
|
||||
default:
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"vppn/fasttime"
|
||||
)
|
||||
|
||||
type connSender struct {
|
||||
conn *net.UDPConn
|
||||
sourceIP byte
|
||||
streamID byte
|
||||
encrypted []byte
|
||||
nonceBuf []byte
|
||||
counter uint64
|
||||
}
|
||||
|
||||
func newConnSender(conn *net.UDPConn, srcIP, streamID byte) *connSender {
|
||||
return &connSender{
|
||||
conn: conn,
|
||||
sourceIP: srcIP,
|
||||
streamID: streamID,
|
||||
encrypted: make([]byte, BUFFER_SIZE),
|
||||
nonceBuf: make([]byte, NONCE_SIZE),
|
||||
counter: uint64(fasttime.Now()) << 30, // Ensure counter is always increasing.
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *connSender) send(packetType byte, packet []byte, dstRoute, viaRoute *route) {
|
||||
if dstRoute.useMediator && viaRoute == nil {
|
||||
log.Printf("Dropping forwarded packet: no mediator.")
|
||||
return
|
||||
}
|
||||
|
||||
cs.counter++
|
||||
|
||||
nonce := Nonce{
|
||||
Timestamp: fasttime.Now(),
|
||||
Counter: cs.counter,
|
||||
SourceIP: cs.sourceIP,
|
||||
DestIP: dstRoute.PeerIP,
|
||||
StreamID: cs.streamID,
|
||||
PacketType: packetType,
|
||||
}
|
||||
|
||||
if dstRoute.useMediator {
|
||||
nonce.ViaIP = viaRoute.PeerIP
|
||||
}
|
||||
|
||||
nonce.Marshal(cs.nonceBuf)
|
||||
|
||||
addr := dstRoute.Addr
|
||||
|
||||
encrypted := encryptPacket(dstRoute.EncSharedKey, cs.nonceBuf, packet, cs.encrypted)
|
||||
if viaRoute != nil {
|
||||
packet, encrypted = encrypted, packet
|
||||
encrypted = encryptPacket(viaRoute.EncSharedKey, cs.nonceBuf, packet, encrypted)
|
||||
addr = viaRoute.Addr
|
||||
}
|
||||
|
||||
if _, err := cs.conn.WriteToUDPAddrPort(encrypted, addr); err != nil {
|
||||
log.Fatalf("Failed to write UDP packet: %v\n%s", err, debug.Stack())
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type safeConnSender struct {
|
||||
lock sync.Mutex
|
||||
sender *connSender
|
||||
}
|
||||
|
||||
func newSafeConnSender(sender *connSender) *safeConnSender {
|
||||
return &safeConnSender{sender: sender}
|
||||
}
|
||||
|
||||
func (s *safeConnSender) send(packetType byte, packet []byte, route, viaRoute *route) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.sender.send(packetType, packet, route, viaRoute)
|
||||
}
|
@ -1,254 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/netip"
|
||||
"time"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
func logState(s connState, msg string, args ...any) {
|
||||
log.Printf("["+s.Name()+"] "+msg, args...)
|
||||
}
|
||||
|
||||
// The connection state corresponds to what we're connected TO.
|
||||
type connState interface {
|
||||
Name() string
|
||||
HandlePacket(wrapper) connState
|
||||
HandleTimeout() connState
|
||||
}
|
||||
|
||||
// Helper functions.
|
||||
|
||||
func newStateFromPeerUpdate(update peerUpdate, data *connData) connState {
|
||||
if update.Peer != nil {
|
||||
return newStateFromPeer(update.Peer, data)
|
||||
}
|
||||
return newConnNull(data)
|
||||
}
|
||||
|
||||
func newStateFromPeer(peer *m.Peer, data *connData) connState {
|
||||
if _, isPublic := netip.AddrFromSlice(peer.PublicIP); isPublic {
|
||||
return newStateServerDown(data, peer)
|
||||
} else if data.server {
|
||||
return newStateClientDown(data, peer)
|
||||
} else {
|
||||
return newStateMediated(data, peer)
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Null Connection //
|
||||
/////////////////////
|
||||
|
||||
type connNull struct{ *connData }
|
||||
|
||||
func newConnNull(data *connData) connState {
|
||||
c := connNull{data}
|
||||
c.peer = nil
|
||||
c.encSharedKey = nil
|
||||
c.publicAddr = netip.AddrPort{}
|
||||
c.pingTimer.Stop()
|
||||
c.timeoutTimer.Stop()
|
||||
c.addr = c.publicAddr
|
||||
c.useMediator = false
|
||||
c.up = false
|
||||
c.route.Store(nil)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c connNull) Name() string { return "NoPeer" }
|
||||
|
||||
func (c connNull) HandlePacket(w wrapper) connState { return c }
|
||||
|
||||
func (c connNull) HandleTimeout() connState {
|
||||
logState(c, "Unexpected timeout.")
|
||||
return c
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Unconnected Server //
|
||||
////////////////////////
|
||||
|
||||
type stateServerDown struct{ *connData }
|
||||
|
||||
func newStateServerDown(data *connData, peer *m.Peer) connState {
|
||||
addr, _ := netip.AddrFromSlice(peer.PublicIP)
|
||||
pubAddr := netip.AddrPortFrom(addr, peer.Port)
|
||||
|
||||
c := stateServerDown{data}
|
||||
c.peer = peer
|
||||
c.encSharedKey = computeSharedKey(peer.EncPubKey, c.encPrivKey)
|
||||
c.publicAddr = pubAddr
|
||||
c.pingTimer.Reset(time.Second) // Ping right away to bring up.
|
||||
c.timeoutTimer.Stop() // No timeouts yet.
|
||||
c.addr = c.publicAddr
|
||||
c.useMediator = false
|
||||
c.up = false
|
||||
c.route.Store(c.Route())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateServerDown) Name() string { return "Server:DOWN" }
|
||||
|
||||
func (c stateServerDown) HandlePacket(w wrapper) connState {
|
||||
switch p := w.Packet.(type) {
|
||||
case *Pong:
|
||||
return newStateServerUp(c.connData, w, p)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateServerDown) HandleTimeout() connState {
|
||||
logState(c, "Unexpected timeout.")
|
||||
return c
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// Connected Server //
|
||||
//////////////////////
|
||||
|
||||
type stateServerUp struct{ *connData }
|
||||
|
||||
func newStateServerUp(data *connData, w wrapper, pong *Pong) connState {
|
||||
c := stateServerUp{data}
|
||||
c.pingTimer.Reset(pingInterval)
|
||||
c.timeoutTimer.Reset(timeoutInterval)
|
||||
c.addr = w.SrcAddr
|
||||
c.useMediator = false
|
||||
c.up = true
|
||||
c.route.Store(c.Route())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateServerUp) Name() string { return "Server:UP" }
|
||||
|
||||
func (c stateServerUp) HandlePacket(w wrapper) connState {
|
||||
switch w.Packet.(type) {
|
||||
case *Pong:
|
||||
c.timeoutTimer.Reset(timeoutInterval)
|
||||
}
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
func (c stateServerUp) HandleTimeout() connState {
|
||||
return newStateFromPeer(c.peer, c.connData)
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Unconnected Client //
|
||||
////////////////////////
|
||||
|
||||
type stateClientDown struct{ *connData }
|
||||
|
||||
func newStateClientDown(data *connData, peer *m.Peer) connState {
|
||||
addr, _ := netip.AddrFromSlice(peer.PublicIP)
|
||||
pubAddr := netip.AddrPortFrom(addr, peer.Port)
|
||||
|
||||
c := stateClientDown{data}
|
||||
c.peer = peer
|
||||
c.publicAddr = pubAddr
|
||||
c.encPrivKey = data.encPrivKey
|
||||
c.encSharedKey = computeSharedKey(peer.EncPubKey, c.encPrivKey)
|
||||
c.addr = c.publicAddr
|
||||
c.useMediator = false
|
||||
c.up = false
|
||||
c.route.Store(c.Route())
|
||||
|
||||
c.pingTimer.Stop() // Conncection is from client => pings incoming.
|
||||
c.timeoutTimer.Stop() // No timeouts yet.
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateClientDown) Name() string { return "Client:DOWN" }
|
||||
|
||||
func (c stateClientDown) HandlePacket(w wrapper) connState {
|
||||
switch w.Packet.(type) {
|
||||
case *Ping:
|
||||
next := newStateClientUp(c.connData, w)
|
||||
c.sendPong(w)
|
||||
return next
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateClientDown) HandleTimeout() connState {
|
||||
logState(c, "Unexpected timeout.")
|
||||
return c
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// Connected Client //
|
||||
//////////////////////
|
||||
|
||||
type stateClientUp struct{ *connData }
|
||||
|
||||
func newStateClientUp(data *connData, w wrapper) connState {
|
||||
c := stateClientUp{data}
|
||||
c.addr = w.SrcAddr
|
||||
c.useMediator = false
|
||||
c.up = true
|
||||
c.route.Store(c.Route())
|
||||
|
||||
c.pingTimer.Stop() // Conncection is from client => pings incoming.
|
||||
c.timeoutTimer.Reset(timeoutInterval)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateClientUp) Name() string { return "Client:UP" }
|
||||
|
||||
func (c stateClientUp) HandlePacket(w wrapper) connState {
|
||||
switch w.Packet.(type) {
|
||||
case *Ping:
|
||||
// The connection is from a client. If the client's address changes, we
|
||||
// should follow that change.
|
||||
if c.addr != w.SrcAddr {
|
||||
c.addr = w.SrcAddr
|
||||
c.route.Store(c.Route())
|
||||
}
|
||||
c.sendPong(w)
|
||||
c.timeoutTimer.Reset(timeoutInterval)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateClientUp) HandleTimeout() connState {
|
||||
return newStateFromPeer(c.peer, c.connData)
|
||||
}
|
||||
|
||||
//////////////
|
||||
// Mediated //
|
||||
//////////////
|
||||
|
||||
type stateMediated struct{ *connData }
|
||||
|
||||
func newStateMediated(data *connData, peer *m.Peer) connState {
|
||||
addr, _ := netip.AddrFromSlice(peer.PublicIP)
|
||||
pubAddr := netip.AddrPortFrom(addr, peer.Port)
|
||||
|
||||
c := stateMediated{data}
|
||||
c.peer = peer
|
||||
c.publicAddr = pubAddr
|
||||
c.encPrivKey = data.encPrivKey
|
||||
c.encSharedKey = computeSharedKey(peer.EncPubKey, c.encPrivKey)
|
||||
c.addr = c.publicAddr
|
||||
c.useMediator = true
|
||||
c.up = true
|
||||
c.route.Store(c.Route())
|
||||
|
||||
c.pingTimer.Stop() // No pings for mediators.
|
||||
c.timeoutTimer.Stop() // No timeouts yet.
|
||||
return c
|
||||
}
|
||||
|
||||
func (c stateMediated) Name() string { return "Mediated:UP" }
|
||||
|
||||
func (c stateMediated) HandlePacket(w wrapper) connState { return c }
|
||||
|
||||
func (c stateMediated) HandleTimeout() connState {
|
||||
logState(c, "Unexpected timeout.")
|
||||
return c
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
||||
func encryptPacket(sharedKey, nonce, packet, out []byte) []byte {
|
||||
out = box.SealAfterPrecomputation(out[:0], packet, (*[24]byte)(nonce), (*[32]byte)(sharedKey))
|
||||
return append(out, nonce...)
|
||||
}
|
||||
|
||||
func decryptPacket(sharedKey, packet, out []byte) (decrypted []byte, ok bool) {
|
||||
cut := len(packet) - NONCE_SIZE
|
||||
decrypted, ok = box.OpenAfterPrecomputation(out[:0], packet[:cut], (*[24]byte)(packet[cut:]), (*[32]byte)(sharedKey))
|
||||
return decrypted, ok
|
||||
}
|
||||
|
||||
func computeSharedKey(peerPubKey, privKey []byte) []byte {
|
||||
shared := [32]byte{}
|
||||
box.Precompute(&shared, (*[32]byte)(peerPubKey), (*[32]byte)(privKey))
|
||||
return shared[:]
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
||||
func TestEncryptDecryptPacket(t *testing.T) {
|
||||
pubKey1, privKey1, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pubKey2, privKey2, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sharedEncKey := [32]byte{}
|
||||
box.Precompute(&sharedEncKey, pubKey2, privKey1)
|
||||
|
||||
sharedDecKey := [32]byte{}
|
||||
box.Precompute(&sharedDecKey, pubKey1, privKey2)
|
||||
|
||||
original := make([]byte, MTU)
|
||||
rand.Read(original)
|
||||
|
||||
nonce := make([]byte, NONCE_SIZE)
|
||||
rand.Read(nonce)
|
||||
|
||||
encrypted := make([]byte, BUFFER_SIZE)
|
||||
encrypted = encryptPacket(sharedEncKey[:], nonce, original, encrypted)
|
||||
|
||||
decrypted := make([]byte, MTU)
|
||||
var ok bool
|
||||
decrypted, ok = decryptPacket(sharedDecKey[:], encrypted, decrypted)
|
||||
if !ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
|
||||
if !bytes.Equal(original, decrypted) {
|
||||
t.Fatal("mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryptPacket(b *testing.B) {
|
||||
_, privKey1, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
pubKey2, _, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
sharedEncKey := [32]byte{}
|
||||
box.Precompute(&sharedEncKey, pubKey2, privKey1)
|
||||
|
||||
original := make([]byte, MTU)
|
||||
rand.Read(original)
|
||||
|
||||
nonce := make([]byte, NONCE_SIZE)
|
||||
rand.Read(nonce)
|
||||
|
||||
encrypted := make([]byte, BUFFER_SIZE)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encrypted = encryptPacket(sharedEncKey[:], nonce, original, encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecryptPacket(b *testing.B) {
|
||||
pubKey1, privKey1, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
pubKey2, privKey2, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
sharedEncKey := [32]byte{}
|
||||
box.Precompute(&sharedEncKey, pubKey2, privKey1)
|
||||
|
||||
sharedDecKey := [32]byte{}
|
||||
box.Precompute(&sharedDecKey, pubKey1, privKey2)
|
||||
|
||||
original := make([]byte, MTU)
|
||||
rand.Read(original)
|
||||
|
||||
nonce := make([]byte, NONCE_SIZE)
|
||||
rand.Read(nonce)
|
||||
|
||||
encrypted := make([]byte, BUFFER_SIZE)
|
||||
encrypted = encryptPacket(sharedEncKey[:], nonce, original, encrypted)
|
||||
|
||||
decrypted := make([]byte, MTU)
|
||||
for i := 0; i < b.N; i++ {
|
||||
decrypted, _ = decryptPacket(sharedDecKey[:], encrypted, decrypted)
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package peer
|
||||
|
||||
const DUP_LIST_SIZE = 32
|
||||
|
||||
type dupList struct {
|
||||
items [DUP_LIST_SIZE]uint64
|
||||
index int
|
||||
}
|
||||
|
||||
func (l *dupList) isDuplicate(in uint64) bool {
|
||||
for _, i := range l.items {
|
||||
if i == in {
|
||||
return true
|
||||
}
|
||||
}
|
||||
l.items[l.index] = in
|
||||
l.index = (l.index + 1) % DUP_LIST_SIZE
|
||||
return false
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package peer
|
||||
|
||||
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 m.PeerConfig) error {
|
||||
return storeJson(pc, peerConfigPath(netName))
|
||||
}
|
||||
|
||||
func storePeerState(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 m.PeerConfig, err error) {
|
||||
return pc, loadJson(peerConfigPath(netName), &pc)
|
||||
}
|
||||
|
||||
func loadPeerState(netName string) (ps m.NetworkState, err error) {
|
||||
return ps, loadJson(peerStatePath(netName), &ps)
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package peer
|
||||
|
||||
const (
|
||||
MAX_IP = 65
|
||||
DEFAULT_PORT = 515
|
||||
NONCE_SIZE = 24
|
||||
KEY_SIZE = 32
|
||||
SIG_SIZE = 64
|
||||
MTU = 1436
|
||||
BUFFER_SIZE = 1536 // Definitely big enough.
|
||||
|
||||
STREAM_DATA = 0
|
||||
STREAM_ROUTING = 1 // Routing queries and responses.
|
||||
)
|
79
peer/main.go
79
peer/main.go
@ -1,79 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
netName string
|
||||
initURL string
|
||||
listenIP string
|
||||
port int
|
||||
)
|
||||
|
||||
flag.StringVar(&netName, "name", "", "[REQUIRED] The network name.")
|
||||
flag.StringVar(&initURL, "init-url", "", "Initializes peer from the hub URL.")
|
||||
flag.StringVar(&listenIP, "listen-ip", "", "IP address to listen on.")
|
||||
flag.IntVar(&port, "port", 0, "Port to listen on.")
|
||||
flag.Parse()
|
||||
|
||||
if netName == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if initURL != "" {
|
||||
mainInit(netName, initURL)
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := NewPeer(netName, listenIP, uint16(port))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create peer: %v", err)
|
||||
}
|
||||
|
||||
peer.Run()
|
||||
}
|
||||
|
||||
func mainInit(netName, initURL string) {
|
||||
if _, err := loadPeerConfig(netName); err == nil {
|
||||
log.Fatalf("Network is already initialized.")
|
||||
}
|
||||
|
||||
resp, err := http.Get(initURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch data from 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 := m.PeerConfig{}
|
||||
if err := json.Unmarshal(data, &peerConfig); err != nil {
|
||||
log.Fatalf("Failed to parse configuration: %v", err)
|
||||
}
|
||||
|
||||
if err := storePeerConfig(netName, peerConfig); err != nil {
|
||||
log.Fatalf("Failed to store configuration: %v", err)
|
||||
}
|
||||
|
||||
log.Print("Initialization successful.")
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package peer
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type Nonce struct {
|
||||
Timestamp int64
|
||||
Counter uint64
|
||||
SourceIP byte
|
||||
ViaIP byte
|
||||
DestIP byte
|
||||
StreamID byte // The stream, see STREAM_* constants
|
||||
PacketType byte // The packet type. See PACKET_* constants.
|
||||
}
|
||||
|
||||
func (nonce *Nonce) Parse(nb []byte) {
|
||||
nonce.Timestamp = *(*int64)(unsafe.Pointer(&nb[0]))
|
||||
nonce.Counter = *(*uint64)(unsafe.Pointer(&nb[8]))
|
||||
nonce.SourceIP = nb[16]
|
||||
nonce.ViaIP = nb[17]
|
||||
nonce.DestIP = nb[18]
|
||||
nonce.StreamID = nb[19]
|
||||
nonce.PacketType = nb[20]
|
||||
}
|
||||
|
||||
func (nonce Nonce) Marshal(buf []byte) {
|
||||
*(*int64)(unsafe.Pointer(&buf[0])) = nonce.Timestamp
|
||||
*(*uint64)(unsafe.Pointer(&buf[8])) = nonce.Counter
|
||||
buf[16] = nonce.SourceIP
|
||||
buf[17] = nonce.ViaIP
|
||||
buf[18] = nonce.DestIP
|
||||
buf[19] = nonce.StreamID
|
||||
buf[20] = nonce.PacketType
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarshalParseNonce(t *testing.T) {
|
||||
nIn := Nonce{
|
||||
Counter: 3212,
|
||||
SourceIP: 34,
|
||||
ViaIP: 20,
|
||||
DestIP: 200,
|
||||
StreamID: 4,
|
||||
PacketType: 44,
|
||||
}
|
||||
|
||||
buf := make([]byte, NONCE_SIZE)
|
||||
nIn.Marshal(buf)
|
||||
|
||||
nOut := Nonce{}
|
||||
nOut.Parse(buf)
|
||||
if nIn != nOut {
|
||||
t.Fatal(nIn, nOut)
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func unmarshalPacket(nonce Nonce, data []byte) Packet {
|
||||
var packet Packet
|
||||
|
||||
switch nonce.PacketType {
|
||||
case PACKET_TYPE_PING:
|
||||
packet = &Ping{}
|
||||
case PACKET_TYPE_PONG:
|
||||
packet = &Pong{}
|
||||
case PACKET_TYPE_LIST_ADDR_REQ:
|
||||
packet = &AddrListReq{}
|
||||
case PACKET_TYPE_LIST_ADDR_RESP:
|
||||
packet = &AddrListResp{}
|
||||
default:
|
||||
// TODO: Log.
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(data) < packet.Size() {
|
||||
log.Printf("Short packet[%d]: %d", nonce.PacketType, len(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
packet.Parse(data[:packet.Size()])
|
||||
|
||||
return packet
|
||||
}
|
138
peer/packets.go
138
peer/packets.go
@ -1,138 +0,0 @@
|
||||
package peer
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
PACKET_TYPE_DATA = 0
|
||||
PACKET_TYPE_PING = 1
|
||||
PACKET_TYPE_PONG = 2
|
||||
PACKET_TYPE_CONN_REQ = 3
|
||||
PACKET_TYPE_CONN_RESP = 4
|
||||
PACKET_TYPE_CONN_RESP_ACK = 5
|
||||
PACKET_TYPE_LIST_ADDR_REQ = 6
|
||||
PACKET_TYPE_LIST_ADDR_RESP = 7
|
||||
|
||||
CONN_REQ_SIZE = 24
|
||||
CONN_RESP_SIZE = 8
|
||||
CONN_RESP_ACK_SIZE = 8
|
||||
)
|
||||
|
||||
type Packet interface {
|
||||
Size() int
|
||||
Parse(buf []byte)
|
||||
Marshal(buf []byte) []byte
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type AddrListReq struct{} // Nothing.
|
||||
|
||||
func (r *AddrListReq) Size() int { return 0 }
|
||||
|
||||
func (r *AddrListReq) Parse(buf []byte) {}
|
||||
|
||||
func (r *AddrListReq) Marshal(buf []byte) []byte { return buf[:0] }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type AddrListResp struct {
|
||||
Addrs [][16]byte // All addresses are IPv6 or mapped IPv6.
|
||||
Ports []uint16 // Ports.
|
||||
}
|
||||
|
||||
func (r *AddrListResp) Size() int {
|
||||
return 18 * MAX_IP
|
||||
}
|
||||
|
||||
func (r *AddrListResp) Parse(buf []byte) {
|
||||
r.Addrs = unsafe.Slice((*[16]byte)(unsafe.Pointer(&buf[0])), MAX_IP)
|
||||
r.Ports = unsafe.Slice((*uint16)(unsafe.Pointer(&buf[16*MAX_IP])), MAX_IP)
|
||||
}
|
||||
|
||||
func (r *AddrListResp) Marshal(buf []byte) []byte {
|
||||
buf = buf[:18:MAX_IP]
|
||||
addrs := unsafe.Slice((*[16]byte)(unsafe.Pointer(&buf[0])), MAX_IP)
|
||||
copy(addrs, r.Addrs)
|
||||
ports := unsafe.Slice((*uint16)(unsafe.Pointer(&buf[16*MAX_IP])), MAX_IP)
|
||||
copy(ports, r.Ports)
|
||||
return buf
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type ConnReq struct {
|
||||
ReqID uint64
|
||||
SentAt int64
|
||||
Tiebreaker int64 // To determine which side is client or server.
|
||||
}
|
||||
|
||||
func (r *ConnReq) Parse(buf []byte) {
|
||||
r.ReqID = *(*uint64)(unsafe.Pointer(&buf[0]))
|
||||
r.SentAt = *(*int64)(unsafe.Pointer(&buf[8]))
|
||||
r.Tiebreaker = *(*int64)(unsafe.Pointer(&buf[16]))
|
||||
}
|
||||
|
||||
func (r ConnReq) Marshal(buf []byte) []byte {
|
||||
buf = buf[:24]
|
||||
*(*uint64)(unsafe.Pointer(&buf[0])) = r.ReqID
|
||||
*(*int64)(unsafe.Pointer(&buf[8])) = r.SentAt
|
||||
*(*int64)(unsafe.Pointer(&buf[16])) = r.Tiebreaker
|
||||
return buf
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type ConnResp struct {
|
||||
ReqID uint64
|
||||
}
|
||||
|
||||
func (r *ConnResp) Parse(buf []byte) {
|
||||
r.ReqID = *(*uint64)(unsafe.Pointer(&buf[0]))
|
||||
}
|
||||
|
||||
func (r ConnResp) Marshal(buf []byte) {
|
||||
*(*uint64)(unsafe.Pointer(&buf[0])) = r.ReqID
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type ConnRespAck = ConnResp
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type Ping struct {
|
||||
SentAt int64 // unix milli
|
||||
}
|
||||
|
||||
func (p *Ping) Size() int { return 8 }
|
||||
|
||||
func (p *Ping) Parse(buf []byte) {
|
||||
p.SentAt = *(*int64)(unsafe.Pointer(&buf[0]))
|
||||
}
|
||||
|
||||
func (p *Ping) Marshal(buf []byte) []byte {
|
||||
buf = buf[:8]
|
||||
*(*int64)(unsafe.Pointer(&buf[0])) = p.SentAt
|
||||
return buf
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type Pong struct {
|
||||
SentAt int64 // unix mili
|
||||
RecvdAt int64 // unix mili
|
||||
}
|
||||
|
||||
func (p *Pong) Size() int { return 16 }
|
||||
|
||||
func (p *Pong) Parse(buf []byte) {
|
||||
p.SentAt = *(*int64)(unsafe.Pointer(&buf[0]))
|
||||
p.RecvdAt = *(*int64)(unsafe.Pointer(&buf[8]))
|
||||
}
|
||||
|
||||
func (p *Pong) Marshal(buf []byte) []byte {
|
||||
buf = buf[:16]
|
||||
*(*int64)(unsafe.Pointer(&buf[0])) = p.SentAt
|
||||
*(*int64)(unsafe.Pointer(&buf[8])) = p.RecvdAt
|
||||
return buf
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func (peer *Peer) ifReader() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
sender = newConnSender(peer.conn, peer.ip, STREAM_DATA)
|
||||
n int
|
||||
destIP byte
|
||||
router = peer.router
|
||||
viaRoute *route
|
||||
route *route
|
||||
iface = peer.iface
|
||||
err error
|
||||
packet = make([]byte, BUFFER_SIZE)
|
||||
version byte
|
||||
)
|
||||
|
||||
for {
|
||||
n, err = iface.Read(packet[:BUFFER_SIZE])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read from interface: %v", err)
|
||||
}
|
||||
|
||||
if n < 20 {
|
||||
log.Printf("Dropping small packet: %d", n)
|
||||
continue
|
||||
}
|
||||
|
||||
packet = packet[:n]
|
||||
version = packet[0] >> 4
|
||||
|
||||
switch version {
|
||||
case 4:
|
||||
destIP = packet[19]
|
||||
case 6:
|
||||
destIP = packet[39]
|
||||
default:
|
||||
log.Printf("Dropping packet with IP version: %d", version)
|
||||
}
|
||||
|
||||
route = router.GetRoute(destIP)
|
||||
if route == nil || !route.Up {
|
||||
log.Printf("Dropping packet for non-existent IP: %d", destIP)
|
||||
continue
|
||||
}
|
||||
|
||||
if route.useMediator {
|
||||
viaRoute = router.GetMediator()
|
||||
if viaRoute == nil || !viaRoute.Up {
|
||||
log.Printf("Dropping packet due to no mediator: %d", destIP)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
viaRoute = nil
|
||||
}
|
||||
|
||||
sender.send(PACKET_TYPE_DATA, packet, route, viaRoute)
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"runtime/debug"
|
||||
"vppn/fasttime"
|
||||
)
|
||||
|
||||
func (peer *Peer) netReader() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
dupLists = [MAX_IP]dupList{}
|
||||
n int
|
||||
srcAddr netip.AddrPort
|
||||
nonce Nonce
|
||||
packet = make([]byte, BUFFER_SIZE)
|
||||
decrypted = make([]byte, BUFFER_SIZE)
|
||||
route *route
|
||||
ok bool
|
||||
err error
|
||||
conn = peer.conn
|
||||
ip = peer.ip
|
||||
counters = [2][256]uint64{} // Counter by stream and IP.
|
||||
)
|
||||
|
||||
NEXT_PACKET:
|
||||
|
||||
n, srcAddr, err = conn.ReadFromUDPAddrPort(packet[:BUFFER_SIZE])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read UDP packet: %v", err)
|
||||
}
|
||||
srcAddr = netip.AddrPortFrom(srcAddr.Addr().Unmap(), srcAddr.Port())
|
||||
|
||||
if n < NONCE_SIZE {
|
||||
log.Printf("Dropping short UDP packet: %d", n)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
packet = packet[:n]
|
||||
nonce.Parse(packet[n-NONCE_SIZE:])
|
||||
|
||||
// Drop after 8 seconds.
|
||||
if nonce.Timestamp < fasttime.Now()-8 {
|
||||
log.Printf("Dropping old packet: %d", nonce.Timestamp)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
if nonce.StreamID > 1 {
|
||||
log.Printf("Dropping invalid stream ID: %+v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
if nonce.Counter <= counters[nonce.StreamID][nonce.SourceIP] {
|
||||
log.Printf("Dropping packet with bad counter: %d (-%d) - %v", nonce.Counter, counters[nonce.StreamID][nonce.SourceIP]-nonce.Counter, srcAddr)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
route = peer.router.GetRoute(nonce.SourceIP)
|
||||
if route == nil {
|
||||
log.Printf("Dropping packet without route: %+v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
decrypted, ok = decryptPacket(route.EncSharedKey, packet, decrypted)
|
||||
if !ok {
|
||||
log.Printf("Failed to decrypt packet: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
// Only updated after we've decrypted.
|
||||
if nonce.Counter > counters[nonce.StreamID][nonce.SourceIP] {
|
||||
counters[nonce.StreamID][nonce.SourceIP] = nonce.Counter
|
||||
}
|
||||
|
||||
switch ip {
|
||||
case nonce.DestIP:
|
||||
goto PROCESS_LOCAL
|
||||
case nonce.ViaIP:
|
||||
goto FORWARD
|
||||
default:
|
||||
log.Printf("Invalid nonce: %+v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
PROCESS_LOCAL:
|
||||
|
||||
switch nonce.StreamID {
|
||||
case STREAM_DATA:
|
||||
goto WRITE_IFACE_DATA
|
||||
case STREAM_ROUTING:
|
||||
goto WRITE_ROUTING_PACKET
|
||||
default:
|
||||
log.Printf("Invalid stream ID: %d", nonce.StreamID)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
WRITE_IFACE_DATA:
|
||||
|
||||
if _, err = peer.iface.Write(decrypted); err != nil {
|
||||
log.Fatalf("Failed to write to interface: %v", err)
|
||||
}
|
||||
|
||||
goto NEXT_PACKET
|
||||
|
||||
WRITE_ROUTING_PACKET:
|
||||
|
||||
peer.router.HandlePacket(srcAddr, nonce, decrypted)
|
||||
|
||||
goto NEXT_PACKET
|
||||
|
||||
FORWARD:
|
||||
|
||||
route = peer.router.GetRoute(nonce.DestIP)
|
||||
if route == nil || !route.Up {
|
||||
log.Printf("Dropping mediated packet, route not available: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
// We don't forward twice.
|
||||
if route.useMediator {
|
||||
log.Printf("Dropping double-forward packet: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
if _, err = conn.WriteToUDPAddrPort(decrypted, route.Addr); err != nil {
|
||||
log.Fatalf("Failed to forward packet: %v", err)
|
||||
}
|
||||
|
||||
goto NEXT_PACKET
|
||||
}
|
65
peer/peer.go
65
peer/peer.go
@ -1,65 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
ip byte // Last byte of IPv4 address.
|
||||
hubAddr string
|
||||
apiKey string
|
||||
isServer bool
|
||||
isMediator bool
|
||||
encPubKey []byte
|
||||
encPrivKey []byte
|
||||
conn *net.UDPConn
|
||||
iface io.ReadWriteCloser
|
||||
|
||||
router *Router
|
||||
}
|
||||
|
||||
func NewPeer(netName, listenIP string, port uint16) (*Peer, error) {
|
||||
conf, err := loadPeerConfig(netName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer := &Peer{
|
||||
ip: conf.PeerIP,
|
||||
hubAddr: conf.HubAddress,
|
||||
isMediator: conf.Mediator,
|
||||
apiKey: conf.APIKey,
|
||||
encPubKey: conf.EncPubKey,
|
||||
encPrivKey: conf.EncPrivKey,
|
||||
}
|
||||
|
||||
_, peer.isServer = netip.AddrFromSlice(conf.PublicIP)
|
||||
|
||||
port = determinePort(conf.Port, port)
|
||||
|
||||
peer.conn, err = openUDPConn(listenIP, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer.router = NewRouter(conf, peer.conn)
|
||||
|
||||
peer.iface, err = openInterface(conf.Network, conf.PeerIP, netName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (p *Peer) Run() {
|
||||
go p.netReader()
|
||||
p.ifReader()
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (r *Router) manageMediator() {
|
||||
var (
|
||||
ip = byte(0)
|
||||
mediators = make([]*route, 0, 32)
|
||||
)
|
||||
|
||||
for range time.Tick(8 * time.Second) {
|
||||
// If the current mediator is up, keep it.
|
||||
route := r.routes[ip].Load()
|
||||
if route != nil && route.Up {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the current mediator is up, keep it.
|
||||
mediators = mediators[:0]
|
||||
|
||||
for i := range r.routes {
|
||||
route := r.routes[i].Load()
|
||||
if route == nil || !route.Mediator {
|
||||
continue
|
||||
}
|
||||
|
||||
mediators = append(mediators, route)
|
||||
}
|
||||
|
||||
if len(mediators) == 0 {
|
||||
r.mediatorIP.Store(nil)
|
||||
} else {
|
||||
ip = mediators[rand.Intn(len(mediators))].PeerIP
|
||||
log.Printf("Got mediator IP: %d", ip)
|
||||
r.mediatorIP.Store(&ip)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarshalParsePingReq(t *testing.T) {
|
||||
in := Ping{
|
||||
SentAt: 4553252,
|
||||
}
|
||||
|
||||
buf := make([]byte, PING_SIZE)
|
||||
in.Marshal(buf)
|
||||
|
||||
out := Ping{}
|
||||
out.Parse(buf)
|
||||
if !reflect.DeepEqual(in, out) {
|
||||
t.Fatal(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalParsePingResp(t *testing.T) {
|
||||
in := Pong{
|
||||
SentAt: 4553252,
|
||||
RecvdAt: 4553253,
|
||||
}
|
||||
|
||||
buf := make([]byte, PONG_SIZE)
|
||||
in.Marshal(buf)
|
||||
|
||||
out := Pong{}
|
||||
out.Parse(buf)
|
||||
if !reflect.DeepEqual(in, out) {
|
||||
t.Fatal(in, out)
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
func (r *Router) pollHub() {
|
||||
u, err := url.Parse(r.conf.HubAddress)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse hub address %s: %v", r.conf.HubAddress, err)
|
||||
}
|
||||
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("", r.conf.APIKey)
|
||||
|
||||
// TODO: Before we start polling, load state from the file system.
|
||||
r._pollHub(client, req)
|
||||
|
||||
for range time.Tick(32 * time.Second) {
|
||||
r._pollHub(client, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) _pollHub(client *http.Client, req *http.Request) {
|
||||
var state m.NetworkState
|
||||
|
||||
log.Printf("Fetching peer state from %s...", r.conf.HubAddress)
|
||||
resp, err := client.Do(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", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range r.conns {
|
||||
if r.conns[i] != nil {
|
||||
r.conns[i].UpdatePeer(peerUpdate{PeerIP: byte(i), Peer: state.Peers[i]})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// A route is used by a peer to send or receive packets. A peer won't send
|
||||
// packets on a route that isn't up, but will process incoming packets on
|
||||
// that route.
|
||||
type route struct {
|
||||
PeerIP byte
|
||||
Up bool
|
||||
Mediator bool
|
||||
EncSharedKey []byte // Shared key for encoding / decoding packets.
|
||||
Addr netip.AddrPort
|
||||
useMediator bool
|
||||
}
|
||||
|
||||
type peerUpdate struct {
|
||||
PeerIP byte
|
||||
*m.Peer // nil => delete.
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Wrapper for routing packets.
|
||||
type wrapper struct {
|
||||
Packet Packet
|
||||
Nonce Nonce
|
||||
SrcAddr netip.AddrPort
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
conf m.PeerConfig
|
||||
|
||||
// Routes used by the peer.
|
||||
conns [MAX_IP]*connHandler
|
||||
routes [MAX_IP]*atomic.Pointer[route]
|
||||
addrs [MAX_IP]*atomic.Pointer[netip.AddrPort]
|
||||
mediatorIP *atomic.Pointer[byte]
|
||||
}
|
||||
|
||||
func NewRouter(conf m.PeerConfig, conn *net.UDPConn) *Router {
|
||||
r := &Router{
|
||||
conf: conf,
|
||||
mediatorIP: &atomic.Pointer[byte]{},
|
||||
}
|
||||
|
||||
for i := range r.routes {
|
||||
r.routes[i] = &atomic.Pointer[route]{}
|
||||
}
|
||||
|
||||
_, isServer := netip.AddrFromSlice(conf.PublicIP)
|
||||
|
||||
sender := newConnSender(conn, conf.PeerIP, STREAM_ROUTING)
|
||||
|
||||
for i := range r.conns {
|
||||
if byte(i) != conf.PeerIP {
|
||||
r.conns[i] = newConnHandler(
|
||||
isServer,
|
||||
byte(i),
|
||||
r.routes,
|
||||
conf.EncPrivKey,
|
||||
newSafeConnSender(sender))
|
||||
}
|
||||
}
|
||||
|
||||
go r.pollHub()
|
||||
if !isServer {
|
||||
go r.manageMediator()
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Peer Methods
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (rm *Router) GetRoute(ip byte) *route {
|
||||
return rm.routes[ip].Load()
|
||||
}
|
||||
|
||||
func (rm *Router) GetMediator() *route {
|
||||
if ip := rm.mediatorIP.Load(); ip != nil {
|
||||
return rm.GetRoute(*ip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) HandlePacket(src netip.AddrPort, nonce Nonce, data []byte) {
|
||||
if nonce.SourceIP == r.conf.PeerIP {
|
||||
log.Printf("Packet to self...")
|
||||
return
|
||||
}
|
||||
|
||||
packet := unmarshalPacket(nonce, data)
|
||||
if packet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.conns[nonce.SourceIP].HandlePacket(wrapper{
|
||||
Packet: packet,
|
||||
Nonce: nonce,
|
||||
SrcAddr: src,
|
||||
})
|
||||
}
|
156
peer/startup.go
156
peer/startup.go
@ -1,156 +0,0 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func determinePort(confPort, portFromCommandLine uint16) uint16 {
|
||||
if portFromCommandLine != 0 {
|
||||
return portFromCommandLine
|
||||
}
|
||||
if confPort != 0 {
|
||||
return confPort
|
||||
}
|
||||
return DEFAULT_PORT
|
||||
}
|
||||
|
||||
func openUDPConn(listenIP string, port uint16) (*net.UDPConn, error) {
|
||||
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", listenIP, port))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct UDP address: %w", err)
|
||||
}
|
||||
|
||||
return net.ListenUDP("udp", myAddr)
|
||||
}
|
||||
|
||||
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(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(1000)
|
||||
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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user