This commit is contained in:
jdl 2024-12-18 21:08:48 +01:00
parent c69b52abf0
commit ee4f5e012c
25 changed files with 3 additions and 1766 deletions

View File

@ -3,6 +3,9 @@
## Roadmap ## Roadmap
* Node: use symmetric encryption after handshake * 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 * Use default port 456
* Remove signing key from hub * Remove signing key from hub
* Peer: UDP hole-punching * Peer: UDP hole-punching

View File

@ -1,9 +0,0 @@
digraph d {
init -> null;
init -> unconnectedServer;
init -> unconnectedClient;
init -> unconnectedMediated;
unconnectedServer -> connectedServer;
unconnectedClient -> connectedClient;
unconnectedMediated -> connectedMediated;
}

View File

@ -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)
}

View File

@ -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:
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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[:]
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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.
)

View File

@ -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.")
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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]})
}
}
}

View File

@ -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
}

View File

@ -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,
})
}

View File

@ -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
}