Add TURN relay support for daemon mode
-turn-url and -turn-secret flags on the daemon; credentials generated using coturn use-auth-secret HMAC-SHA1 scheme (same as browser mode). ICEServers field on mesh.Mesh threads extra ICE servers through to every PeerConnection created by the anchor client. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -229,7 +229,7 @@ func runOnce(ctx context.Context, anchorURL, netHash string, id *crypto.Identity
|
||||
mu.Lock()
|
||||
if sess == nil {
|
||||
// Answerer: we haven't created a session yet, do it now.
|
||||
pc, err := newPC()
|
||||
pc, err := newPC(m.ICEServers)
|
||||
if err != nil {
|
||||
mu.Unlock()
|
||||
log.Printf("anchor: new PC for answerer: %v", err)
|
||||
@@ -412,7 +412,7 @@ func dispatchSignaling(
|
||||
// startOffer creates a session, sends our ekey, waits up to ekeyTimeout for
|
||||
// the peer's ekey, then sends the offer (ephemeral or static).
|
||||
func startOffer(ctx context.Context, peerID proto.PeerID, id *crypto.Identity, m *mesh.Mesh, s *sender) (*peerSession, error) {
|
||||
pc, err := newPC()
|
||||
pc, err := newPC(m.ICEServers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -473,7 +473,7 @@ func startOffer(ctx context.Context, peerID proto.PeerID, id *crypto.Identity, m
|
||||
|
||||
// answerOffer processes an incoming offer and returns the PeerConnection.
|
||||
func answerOffer(ctx context.Context, payload proto.SignalingPayload, fromID proto.PeerID, id *crypto.Identity, m *mesh.Mesh, s *sender, sess *peerSession) (*webrtc.PeerConnection, error) {
|
||||
pc, err := newPC()
|
||||
pc, err := newPC(m.ICEServers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -618,10 +618,9 @@ func hashNetName(name string) string {
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
func newPC() (*webrtc.PeerConnection, error) {
|
||||
return webrtc.NewPeerConnection(webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}},
|
||||
})
|
||||
func newPC(extra []webrtc.ICEServer) (*webrtc.PeerConnection, error) {
|
||||
servers := append([]webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}, extra...)
|
||||
return webrtc.NewPeerConnection(webrtc.Configuration{ICEServers: servers})
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
"github.com/waste-go/internal/store"
|
||||
)
|
||||
|
||||
// ICEServer mirrors webrtc.ICEServer so callers don't import pion directly.
|
||||
type ICEServer = webrtc.ICEServer
|
||||
|
||||
// PeerConn is a live connection to one peer.
|
||||
type PeerConn struct {
|
||||
Info proto.PeerInfo
|
||||
@@ -33,7 +36,8 @@ type Mesh struct {
|
||||
InviteString string // the invite this peer used to join (sent in hello to other peers)
|
||||
// ScanFiles overrides ScanShareDir when set — allows the manager to inject
|
||||
// multi-share scanning without the mesh needing to know about shares.json.
|
||||
ScanFiles func() []proto.FileEntry
|
||||
ScanFiles func() []proto.FileEntry
|
||||
ICEServers []ICEServer // extra ICE servers (e.g. TURN); appended to the default STUN entry
|
||||
|
||||
mu sync.RWMutex
|
||||
peers map[proto.PeerID]*PeerConn
|
||||
|
||||
@@ -6,13 +6,20 @@ package netmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/waste-go/internal/anchor"
|
||||
"github.com/waste-go/internal/crypto"
|
||||
@@ -28,6 +35,8 @@ type Config struct {
|
||||
StoreDir string // base directory for per-network SQLite files
|
||||
AnchorURL string // WebSocket anchor URL used for all networks
|
||||
ShareDir string // default share directory; overridden per network via Join or SetShareDir
|
||||
TurnURL string // optional TURN server URL, e.g. "turn:your-vps:3478"
|
||||
TurnSecret string // shared secret for coturn use-auth-secret HMAC credential
|
||||
}
|
||||
|
||||
// Network is a single joined network context.
|
||||
@@ -113,6 +122,9 @@ func (mgr *Manager) Join(name, shareDir string) (string, error) {
|
||||
m.DownloadDir = filepath.Join(mgr.cfg.StoreDir, "downloads-"+netID_full)
|
||||
capturedNetID := netID
|
||||
m.ScanFiles = func() []proto.FileEntry { return mgr.ScanAllShares(capturedNetID) }
|
||||
if ice := mgr.turnICEServers(); ice != nil {
|
||||
m.ICEServers = ice
|
||||
}
|
||||
|
||||
// Forward all mesh events to the Manager's fan-out, tagging with network_id.
|
||||
meshEvents := m.Subscribe()
|
||||
@@ -198,6 +210,9 @@ func (mgr *Manager) JoinByHash(netHash64, shareDir string) (string, error) {
|
||||
m.DownloadDir = filepath.Join(mgr.cfg.StoreDir, "downloads-"+netID)
|
||||
capturedNetID2 := netID
|
||||
m.ScanFiles = func() []proto.FileEntry { return mgr.ScanAllShares(capturedNetID2) }
|
||||
if ice := mgr.turnICEServers(); ice != nil {
|
||||
m.ICEServers = ice
|
||||
}
|
||||
|
||||
meshEvents := m.Subscribe()
|
||||
go func() {
|
||||
@@ -411,6 +426,26 @@ func (mgr *Manager) emit(msg proto.IpcMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
// turnICEServers returns TURN ICE servers if TurnURL and TurnSecret are set,
|
||||
// using coturn's use-auth-secret HMAC-SHA1 time-limited credential scheme.
|
||||
// Returns nil if TURN is not configured.
|
||||
func (mgr *Manager) turnICEServers() []webrtc.ICEServer {
|
||||
if mgr.cfg.TurnURL == "" || mgr.cfg.TurnSecret == "" {
|
||||
return nil
|
||||
}
|
||||
// Username = Unix timestamp 1 hour from now.
|
||||
expiry := strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10)
|
||||
mac := hmac.New(sha1.New, []byte(mgr.cfg.TurnSecret))
|
||||
mac.Write([]byte(expiry))
|
||||
credential := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
return []webrtc.ICEServer{{
|
||||
URLs: []string{mgr.cfg.TurnURL},
|
||||
Username: expiry,
|
||||
Credential: credential,
|
||||
CredentialType: webrtc.ICECredentialTypePassword,
|
||||
}}
|
||||
}
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func hashNetName(name string) string {
|
||||
|
||||
Reference in New Issue
Block a user