Ed25519/X25519/ChaCha20-Poly1305 crypto, peer handshake, mesh state, IPC server, relay server, and NAT stub. Builds clean on Go 1.22+. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
// Package nat handles relay registration and future hole-punching.
|
|
package nat
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/waste-go/internal/mesh"
|
|
"github.com/waste-go/internal/proto"
|
|
)
|
|
|
|
// Run connects to the relay server (if configured) and keeps the connection alive.
|
|
// Pass an empty relayAddr to skip relay entirely (LAN-only mode).
|
|
func Run(m *mesh.Mesh, relayAddr string) error {
|
|
if relayAddr == "" {
|
|
log.Println("nat: no relay configured, running in LAN-only mode")
|
|
select {} // block forever — task stays alive
|
|
}
|
|
|
|
for {
|
|
log.Printf("nat: connecting to relay at %s", relayAddr)
|
|
if err := connectRelay(relayAddr, m); err != nil {
|
|
log.Printf("nat: relay error: %v", err)
|
|
}
|
|
log.Printf("nat: reconnecting to relay in 10s...")
|
|
time.Sleep(10 * time.Second)
|
|
}
|
|
}
|
|
|
|
func connectRelay(addr string, m *mesh.Mesh) error {
|
|
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
|
|
if err != nil {
|
|
return fmt.Errorf("dial relay: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
id := m.Identity
|
|
enc := json.NewEncoder(conn)
|
|
dec := json.NewDecoder(bufio.NewReader(conn))
|
|
|
|
// Register with the relay
|
|
reg := proto.RelayMessage{
|
|
Type: proto.RelayRegister,
|
|
Peer: ptr(id.PeerInfo()),
|
|
Signature: id.Sign([]byte(id.PeerID())),
|
|
}
|
|
if err := enc.Encode(reg); err != nil {
|
|
return fmt.Errorf("sending register: %w", err)
|
|
}
|
|
log.Printf("nat: registered with relay as %s", id.PeerID().Short())
|
|
|
|
// Ask for the current peer list (bootstrap)
|
|
if err := enc.Encode(proto.RelayMessage{Type: proto.RelayListPeers}); err != nil {
|
|
return fmt.Errorf("sending list_peers: %w", err)
|
|
}
|
|
|
|
// Read relay messages
|
|
for {
|
|
var msg proto.RelayMessage
|
|
if err := dec.Decode(&msg); err != nil {
|
|
return fmt.Errorf("reading relay message: %w", err)
|
|
}
|
|
|
|
switch msg.Type {
|
|
case proto.RelayPeerList:
|
|
log.Printf("nat: relay gave us %d peer hints", len(msg.Peers))
|
|
// TODO: attempt direct connections to each peer hint
|
|
|
|
case proto.RelayForwarded:
|
|
from := "unknown"
|
|
if msg.From != nil {
|
|
from = msg.From.Short()
|
|
}
|
|
log.Printf("nat: relayed envelope from %s — direct connection not yet implemented", from)
|
|
// TODO: decrypt and process as PeerMessage
|
|
|
|
case proto.RelayError:
|
|
log.Printf("nat: relay error: %s", msg.Message)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ptr[T any](v T) *T { return &v }
|