feat: SDWAN data plane + UDP punch port fix + TUN reader
SDWAN: - protocol: add SDWANConfig/SDWANPeer/SDWANPacket structs, MsgTunnel type - server: sdwan.go (JSON file store), sdwan_api.go (Get/Set/broadcast/route) - server: push SDWAN config on login, announce peer online/offline events - server: RouteSDWANPacket routes TUN packets between nodes via signaling - client: TUN device setup (optun), tunReadLoop reads IP packets - client: handle SDWANConfig/SDWANPeer/SDWANDel push messages - client: apply routes (per-node /32 + broad CIDR fallback) UDP punch fix: - nat/detect: capture LocalPort from STUN UDP socket for punch binding - client: pass publicPort + localPort through login and punch config - coordinator: include PublicPort in PunchParams for both sides - protocol: add PublicPort to LoginReq and ReportBasic Other: - server: use client-reported PublicIP instead of raw r.RemoteAddr - server: update PublicIP/Port from ReportBasic if provided - client: config file loading with zero-value defaults backfill - .gitignore: exclude run/, *.pid, *.log, sdwan.json - go.mod: add golang.org/x/sys for TUN ioctl
This commit is contained in:
@@ -3,6 +3,7 @@ package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -22,6 +23,7 @@ type NodeInfo struct {
|
||||
Version string
|
||||
NATType protocol.NATType
|
||||
PublicIP string
|
||||
PublicPort int
|
||||
LanIP string
|
||||
OS string
|
||||
Mac string
|
||||
@@ -46,18 +48,26 @@ func (n *NodeInfo) IsOnline() bool {
|
||||
|
||||
// Server is the INP2P signaling server.
|
||||
type Server struct {
|
||||
cfg config.ServerConfig
|
||||
nodes map[string]*NodeInfo // node name → info
|
||||
mu sync.RWMutex
|
||||
upgrader websocket.Upgrader
|
||||
quit chan struct{}
|
||||
cfg config.ServerConfig
|
||||
nodes map[string]*NodeInfo // node name → info
|
||||
mu sync.RWMutex
|
||||
upgrader websocket.Upgrader
|
||||
quit chan struct{}
|
||||
sdwanPath string
|
||||
sdwan *sdwanStore
|
||||
}
|
||||
|
||||
// New creates a new server.
|
||||
func New(cfg config.ServerConfig) *Server {
|
||||
sdwanPath := "sdwan.json"
|
||||
if cfg.DBPath != "" {
|
||||
sdwanPath = cfg.DBPath + ".sdwan.json"
|
||||
}
|
||||
return &Server{
|
||||
cfg: cfg,
|
||||
nodes: make(map[string]*NodeInfo),
|
||||
cfg: cfg,
|
||||
nodes: make(map[string]*NodeInfo),
|
||||
sdwanPath: sdwanPath,
|
||||
sdwan: newSDWANStore(sdwanPath),
|
||||
upgrader: websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
},
|
||||
@@ -170,7 +180,8 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
|
||||
ShareBandwidth: loginReq.ShareBandwidth,
|
||||
RelayEnabled: loginReq.RelayEnabled,
|
||||
SuperRelay: loginReq.SuperRelay,
|
||||
PublicIP: r.RemoteAddr, // will be updated by NAT detect
|
||||
PublicIP: loginReq.PublicIP,
|
||||
PublicPort: loginReq.PublicPort,
|
||||
LoginTime: time.Now(),
|
||||
LastHeartbeat: time.Now(),
|
||||
Conn: conn,
|
||||
@@ -178,6 +189,12 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
|
||||
s.nodes[loginReq.Node] = node
|
||||
s.mu.Unlock()
|
||||
|
||||
if node.PublicIP == "" {
|
||||
// fallback to TCP remote addr if client didn't provide
|
||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
node.PublicIP = host
|
||||
}
|
||||
|
||||
// Send login response
|
||||
conn.Write(protocol.MsgLogin, protocol.SubLoginRsp, protocol.LoginRsp{
|
||||
Error: 0,
|
||||
@@ -187,12 +204,19 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
|
||||
Node: loginReq.Node,
|
||||
})
|
||||
|
||||
log.Printf("[server] login ok: node=%s, natType=%s, relay=%v, super=%v, version=%s",
|
||||
loginReq.Node, loginReq.NATType, loginReq.RelayEnabled, loginReq.SuperRelay, loginReq.Version)
|
||||
log.Printf("[server] login ok: node=%s, natType=%s, relay=%v, super=%v, version=%s, public=%s:%d",
|
||||
loginReq.Node, loginReq.NATType, loginReq.RelayEnabled, loginReq.SuperRelay, loginReq.Version, node.PublicIP, node.PublicPort)
|
||||
|
||||
// Notify other nodes
|
||||
s.broadcastNodeOnline(loginReq.Node)
|
||||
|
||||
// Push current SDWAN config right after login (if exists and enabled)
|
||||
if cfg := s.sdwan.get(); cfg.Enabled && cfg.GatewayCIDR != "" {
|
||||
_ = conn.Write(protocol.MsgPush, protocol.SubPushSDWANConfig, cfg)
|
||||
}
|
||||
// Event-driven SDWAN peer notification
|
||||
s.announceSDWANNodeOnline(loginReq.Node)
|
||||
|
||||
// Register message handlers
|
||||
s.registerHandlers(conn, node)
|
||||
|
||||
@@ -207,6 +231,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
|
||||
delete(s.nodes, loginReq.Node)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
s.announceSDWANNodeOffline(loginReq.Node)
|
||||
log.Printf("[server] %s offline", loginReq.Node)
|
||||
}
|
||||
|
||||
@@ -235,6 +260,14 @@ func (s *Server) registerHandlers(conn *signal.Conn, node *NodeInfo) {
|
||||
node.mu.Unlock()
|
||||
log.Printf("[server] ReportBasic from %s: os=%s lanIP=%s", node.Name, report.OS, report.LanIP)
|
||||
|
||||
// Update public IP/port from NAT report (if provided)
|
||||
if report.PublicIP != "" {
|
||||
node.mu.Lock()
|
||||
node.PublicIP = report.PublicIP
|
||||
node.PublicPort = report.PublicPort
|
||||
node.mu.Unlock()
|
||||
}
|
||||
|
||||
// Always respond (official OpenP2P bug: not responding causes client to disconnect)
|
||||
return conn.Write(protocol.MsgReport, protocol.SubReportBasic, protocol.ReportBasicRsp{Error: 0})
|
||||
})
|
||||
@@ -275,6 +308,16 @@ func (s *Server) registerHandlers(conn *signal.Conn, node *NodeInfo) {
|
||||
protocol.DecodePayload(data, &req)
|
||||
return s.handleRelayNodeReq(conn, node, req)
|
||||
})
|
||||
|
||||
// SDWAN data plane packet relay (server as control-plane router)
|
||||
conn.OnMessage(protocol.MsgTunnel, protocol.SubTunnelSDWANData, func(data []byte) error {
|
||||
var pkt protocol.SDWANPacket
|
||||
if err := protocol.DecodePayload(data, &pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
s.RouteSDWANPacket(node, pkt)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// handleRelayNodeReq finds and returns the best relay node.
|
||||
@@ -317,9 +360,9 @@ func (s *Server) PushConnect(fromNode *NodeInfo, toNodeName string, app protocol
|
||||
|
||||
// Push connect request to the destination
|
||||
req := protocol.ConnectReq{
|
||||
From: fromNode.Name,
|
||||
To: toNodeName,
|
||||
FromIP: fromNode.PublicIP,
|
||||
From: fromNode.Name,
|
||||
To: toNodeName,
|
||||
FromIP: fromNode.PublicIP,
|
||||
Peer: protocol.PunchParams{
|
||||
IP: fromNode.PublicIP,
|
||||
NATType: fromNode.NATType,
|
||||
|
||||
Reference in New Issue
Block a user