Files
waste-go/internal/nat/nat.go
Fredrik Johansson b3a4af15ca Initial commit: waste-go skeleton
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>
2026-06-21 16:14:07 +02:00

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 }