Files
inp2p/internal/server/sdwan.go
openclaw 5568ea67d9 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
2026-03-02 17:48:05 +08:00

88 lines
1.6 KiB
Go

package server
import (
"encoding/json"
"errors"
"os"
"sort"
"sync"
"time"
"github.com/openp2p-cn/inp2p/pkg/protocol"
)
type sdwanStore struct {
mu sync.RWMutex
path string
cfg protocol.SDWANConfig
}
func newSDWANStore(path string) *sdwanStore {
s := &sdwanStore{path: path}
_ = s.load()
return s
}
func (s *sdwanStore) load() error {
s.mu.Lock()
defer s.mu.Unlock()
b, err := os.ReadFile(s.path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
var c protocol.SDWANConfig
if err := json.Unmarshal(b, &c); err != nil {
return err
}
s.cfg = normalizeSDWAN(c)
return nil
}
func (s *sdwanStore) save(cfg protocol.SDWANConfig) error {
s.mu.Lock()
defer s.mu.Unlock()
cfg = normalizeSDWAN(cfg)
cfg.UpdatedAt = time.Now().Unix()
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(s.path, b, 0644); err != nil {
return err
}
s.cfg = cfg
return nil
}
func (s *sdwanStore) get() protocol.SDWANConfig {
s.mu.RLock()
defer s.mu.RUnlock()
return s.cfg
}
func normalizeSDWAN(c protocol.SDWANConfig) protocol.SDWANConfig {
if c.Mode == "" {
c.Mode = "hub"
}
if !c.Enabled {
c.Enabled = true
}
// de-dup nodes by node name, keep last and sort for stable output
m := make(map[string]string)
for _, n := range c.Nodes {
if n.Node == "" {
continue
}
m[n.Node] = n.IP
}
c.Nodes = c.Nodes[:0]
for node, ip := range m {
c.Nodes = append(c.Nodes, protocol.SDWANNode{Node: node, IP: ip})
}
sort.Slice(c.Nodes, func(i, j int) bool { return c.Nodes[i].Node < c.Nodes[j].Node })
return c
}