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