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
140 lines
4.1 KiB
Go
140 lines
4.1 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/openp2p-cn/inp2p/pkg/protocol"
|
|
)
|
|
|
|
// ConnectCoordinator handles the complete punch coordination flow:
|
|
// 1. Client A sends ConnectReq to server
|
|
// 2. Server looks up Client B
|
|
// 3. Server pushes PunchStart to BOTH A and B simultaneously
|
|
// 4. Both sides call punch.Connect() at the same time
|
|
// 5. Success/failure reported back via PunchResult
|
|
|
|
// HandleConnectReq processes a connection request from node A to node B.
|
|
func (s *Server) HandleConnectReq(from *NodeInfo, req protocol.ConnectReq) error {
|
|
to := s.GetNode(req.To)
|
|
if to == nil || !to.IsOnline() {
|
|
// Peer offline — respond with error
|
|
from.Conn.Write(protocol.MsgPush, protocol.SubPushConnectRsp, protocol.ConnectRsp{
|
|
Error: 1,
|
|
Detail: fmt.Sprintf("node %s offline", req.To),
|
|
From: req.To,
|
|
To: req.From,
|
|
})
|
|
return &NodeOfflineError{Node: req.To}
|
|
}
|
|
|
|
log.Printf("[coord] %s → %s: coordinating punch", from.Name, to.Name)
|
|
|
|
// Build punch parameters for both sides
|
|
from.mu.RLock()
|
|
fromParams := protocol.PunchParams{
|
|
IP: from.PublicIP,
|
|
Port: from.PublicPort,
|
|
NATType: from.NATType,
|
|
HasIPv4: from.HasIPv4,
|
|
}
|
|
from.mu.RUnlock()
|
|
|
|
to.mu.RLock()
|
|
toParams := protocol.PunchParams{
|
|
IP: to.PublicIP,
|
|
Port: to.PublicPort,
|
|
NATType: to.NATType,
|
|
HasIPv4: to.HasIPv4,
|
|
}
|
|
to.mu.RUnlock()
|
|
|
|
// Check if punch is possible
|
|
if !protocol.CanPunch(fromParams.NATType, toParams.NATType) {
|
|
log.Printf("[coord] %s(%s) ↔ %s(%s): punch impossible, suggesting relay",
|
|
from.Name, fromParams.NATType, to.Name, toParams.NATType)
|
|
// Respond to A with B's info but mark that punch is unlikely
|
|
from.Conn.Write(protocol.MsgPush, protocol.SubPushConnectRsp, protocol.ConnectRsp{
|
|
Error: 0,
|
|
From: to.Name,
|
|
To: from.Name,
|
|
Peer: toParams,
|
|
Detail: "punch-unlikely",
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Push PunchStart to BOTH sides simultaneously
|
|
punchID := fmt.Sprintf("%s-%s-%d", from.Name, to.Name, time.Now().UnixMilli())
|
|
|
|
// Tell B about A (so B starts punching toward A)
|
|
punchToB := protocol.ConnectReq{
|
|
From: from.Name,
|
|
To: to.Name,
|
|
FromIP: from.PublicIP,
|
|
Peer: fromParams,
|
|
AppName: req.AppName,
|
|
Protocol: req.Protocol,
|
|
SrcPort: req.SrcPort,
|
|
DstHost: req.DstHost,
|
|
DstPort: req.DstPort,
|
|
}
|
|
if err := to.Conn.Write(protocol.MsgPush, protocol.SubPushConnectReq, punchToB); err != nil {
|
|
log.Printf("[coord] push to %s failed: %v", to.Name, err)
|
|
}
|
|
|
|
// Tell A about B (so A starts punching toward B)
|
|
rspToA := protocol.ConnectRsp{
|
|
Error: 0,
|
|
From: to.Name,
|
|
To: from.Name,
|
|
Peer: toParams,
|
|
}
|
|
if err := from.Conn.Write(protocol.MsgPush, protocol.SubPushConnectRsp, rspToA); err != nil {
|
|
log.Printf("[coord] rsp to %s failed: %v", from.Name, err)
|
|
}
|
|
|
|
log.Printf("[coord] punch started: %s(%s:%s) ↔ %s(%s:%s) id=%s",
|
|
from.Name, fromParams.IP, fromParams.NATType,
|
|
to.Name, toParams.IP, toParams.NATType,
|
|
punchID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// HandleEditApp pushes an app configuration to a node, triggering tunnel creation.
|
|
func (s *Server) HandleEditApp(nodeName string, app protocol.AppConfig) error {
|
|
node := s.GetNode(nodeName)
|
|
if node == nil || !node.IsOnline() {
|
|
return &NodeOfflineError{Node: nodeName}
|
|
}
|
|
|
|
log.Printf("[coord] push EditApp to %s: %s (:%d → %s:%d)",
|
|
nodeName, app.AppName, app.SrcPort, app.PeerNode, app.DstPort)
|
|
|
|
return node.Conn.Write(protocol.MsgPush, protocol.SubPushEditApp, app)
|
|
}
|
|
|
|
// HandleDeleteApp pushes app deletion to a node.
|
|
func (s *Server) HandleDeleteApp(nodeName string, appName string) error {
|
|
node := s.GetNode(nodeName)
|
|
if node == nil || !node.IsOnline() {
|
|
return &NodeOfflineError{Node: nodeName}
|
|
}
|
|
|
|
return node.Conn.Write(protocol.MsgPush, protocol.SubPushDeleteApp, struct {
|
|
AppName string `json:"appName"`
|
|
}{AppName: appName})
|
|
}
|
|
|
|
// HandleReportApps pushes a report-apps request to a node.
|
|
func (s *Server) HandleReportApps(nodeName string) error {
|
|
node := s.GetNode(nodeName)
|
|
if node == nil || !node.IsOnline() {
|
|
return &NodeOfflineError{Node: nodeName}
|
|
}
|
|
|
|
return node.Conn.Write(protocol.MsgPush, protocol.SubPushReportApps, nil)
|
|
}
|