Files
inp2p/internal/server/coordinator.go
openclaw 91e3d4da2a feat: INP2P v0.1.0 — complete P2P tunneling system
Core modules (M1-M6):
- pkg/protocol: message format, encoding, NAT type enums
- pkg/config: server/client config structs, env vars, validation
- pkg/auth: CRC64 token, TOTP gen/verify, one-time relay tokens
- pkg/nat: UDP/TCP STUN client and server
- pkg/signal: WSS message dispatch, sync request/response
- pkg/punch: UDP/TCP hole punching + priority chain
- pkg/mux: stream multiplexer (7B frame: StreamID+Flags+Len)
- pkg/tunnel: mux-based port forwarding with stats
- pkg/relay: relay manager with TOTP auth + session bridging
- internal/server: signaling server (login/heartbeat/report/coordinator)
- internal/client: client (NAT detect/login/punch/relay/reconnect)
- cmd/inp2ps + cmd/inp2pc: main entrypoints with graceful shutdown

All tests pass: 16 tests across 5 packages
Code: 3559 lines core + 861 lines tests = 19 source files
2026-03-02 15:13:22 +08:00

138 lines
4.0 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,
NATType: from.NATType,
HasIPv4: from.HasIPv4,
}
from.mu.RUnlock()
to.mu.RLock()
toParams := protocol.PunchParams{
IP: to.PublicIP,
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)
}