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:
2026-03-02 17:48:05 +08:00
parent 673e354fe5
commit 5568ea67d9
12 changed files with 680 additions and 37 deletions

View File

@@ -51,6 +51,34 @@ func main() {
var fileCfg config.ClientConfig
if err := json.Unmarshal(data, &fileCfg); err == nil {
cfg = fileCfg
// fill defaults for missing fields
if cfg.ServerPort == 0 {
cfg.ServerPort = config.DefaultWSPort
}
if cfg.STUNUDP1 == 0 {
cfg.STUNUDP1 = config.DefaultSTUNUDP1
}
if cfg.STUNUDP2 == 0 {
cfg.STUNUDP2 = config.DefaultSTUNUDP2
}
if cfg.STUNTCP1 == 0 {
cfg.STUNTCP1 = config.DefaultSTUNTCP1
}
if cfg.STUNTCP2 == 0 {
cfg.STUNTCP2 = config.DefaultSTUNTCP2
}
if cfg.RelayPort == 0 {
cfg.RelayPort = config.DefaultRelayPort
}
if cfg.MaxRelayLoad == 0 {
cfg.MaxRelayLoad = config.DefaultMaxRelayLoad
}
if cfg.ShareBandwidth == 0 {
cfg.ShareBandwidth = 10
}
if cfg.LogLevel == 0 {
cfg.LogLevel = 1
}
log.Printf("[main] loaded config from %s", *configFile)
}
}

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
@@ -16,6 +17,7 @@ import (
"github.com/openp2p-cn/inp2p/pkg/auth"
"github.com/openp2p-cn/inp2p/pkg/config"
"github.com/openp2p-cn/inp2p/pkg/nat"
"github.com/openp2p-cn/inp2p/pkg/protocol"
)
func main() {
@@ -91,6 +93,31 @@ func main() {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status":"ok","version":"%s","nodes":%d}`, config.Version, len(srv.GetOnlineNodes()))
})
mux.HandleFunc("/api/v1/sdwans", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(srv.GetSDWAN())
})
mux.HandleFunc("/api/v1/sdwan/edit", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req protocol.SDWANConfig
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := srv.SetSDWAN(req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "ok"})
})
// ─── HTTP Listener ───
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.WSPort))