feat: audit api, sdwan persist, relay fallback updates

This commit is contained in:
2026-03-06 14:47:03 +08:00
parent e96a2e5dd9
commit 57b4dadd42
26 changed files with 991 additions and 183 deletions

View File

@@ -3,6 +3,7 @@ package client
import (
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net"
@@ -45,6 +46,7 @@ type Client struct {
sdwanStop chan struct{}
tunMu sync.Mutex
tunFile *os.File
sdwanPath string
quit chan struct{}
wg sync.WaitGroup
}
@@ -53,6 +55,7 @@ type Client struct {
func New(cfg config.ClientConfig) *Client {
c := &Client{
cfg: cfg,
sdwanPath: "/etc/inp2p/sdwan.json",
natType: protocol.NATUnknown,
tunnels: make(map[string]*tunnel.Tunnel),
sdwanStop: make(chan struct{}),
@@ -62,7 +65,7 @@ func New(cfg config.ClientConfig) *Client {
}
if cfg.RelayEnabled {
c.relayMgr = relay.NewManager(cfg.RelayPort, true, cfg.SuperRelay, cfg.MaxRelayLoad, cfg.Token)
c.relayMgr = relay.NewManager(cfg.RelayPort, true, cfg.SuperRelay, cfg.MaxRelayLoad, cfg.Token, cfg.ShareBandwidth)
}
return c
@@ -95,7 +98,7 @@ func (c *Client) connectAndRun() error {
c.publicIP = natResult.PublicIP
c.publicPort = natResult.Port1
c.localPort = natResult.LocalPort
log.Printf("[client] SENDING_LOGIN_TOKEN=%d NAT type=%s, publicIP=%s, publicPort=%d, localPort=%d", c.natType, c.publicIP, c.publicPort, c.localPort)
log.Printf("[client] SENDING_LOGIN_TOKEN=%d NAT type=%s, publicIP=%s, publicPort=%d, localPort=%d", c.cfg.Token, c.natType, c.publicIP, c.publicPort, c.localPort)
// 2. WSS Connect
scheme := "ws"
@@ -130,12 +133,14 @@ func (c *Client) connectAndRun() error {
loginReq := protocol.LoginReq{
Node: c.cfg.Node,
Token: c.cfg.Token,
NodeSecret: c.cfg.NodeSecret,
User: c.cfg.User,
Version: config.Version,
NATType: c.natType,
ShareBandwidth: c.cfg.ShareBandwidth,
RelayEnabled: c.cfg.RelayEnabled,
SuperRelay: c.cfg.SuperRelay,
RelayOfficial: c.cfg.RelayOfficial,
PublicIP: c.publicIP,
PublicPort: c.publicPort,
}
@@ -236,7 +241,6 @@ func (c *Client) registerHandlers() {
return nil
}
log.Printf("[client] sdwan config received: gateway=%s nodes=%d mode=%s", cfg.GatewayCIDR, len(cfg.Nodes), cfg.Mode)
_ = os.WriteFile("sdwan.json", data[protocol.HeaderSize:], 0644)
// apply control+data plane
if err := c.applySDWAN(cfg); err != nil {
@@ -396,7 +400,7 @@ func (c *Client) connectApp(app config.AppConfig) {
)
if err != nil {
log.Printf("[client] connect coordination failed for %s: %v", app.PeerNode, err)
c.tryRelay(app)
c.tryRelay(app, "tenant")
return
}
@@ -404,7 +408,7 @@ func (c *Client) connectApp(app config.AppConfig) {
protocol.DecodePayload(rspData, &rsp)
if rsp.Error != 0 {
log.Printf("[client] connect denied: %s", rsp.Detail)
c.tryRelay(app)
c.tryRelay(app, "tenant")
return
}
@@ -420,7 +424,7 @@ func (c *Client) connectApp(app config.AppConfig) {
if result.Error != nil {
log.Printf("[client] punch failed for %s: %v", app.PeerNode, result.Error)
c.tryRelay(app)
c.tryRelay(app, "tenant")
c.reportConnect(app, protocol.ReportConnect{
PeerNode: app.PeerNode, Error: result.Error.Error(),
NATType: c.natType, PeerNATType: rsp.Peer.NATType,
@@ -448,12 +452,12 @@ func (c *Client) connectApp(app config.AppConfig) {
}
// tryRelay attempts to use a relay node.
func (c *Client) tryRelay(app config.AppConfig) {
log.Printf("[client] trying relay for %s", app.PeerNode)
func (c *Client) tryRelay(app config.AppConfig, mode string) {
log.Printf("[client] trying relay(%s) for %s", mode, app.PeerNode)
rspData, err := c.conn.Request(
protocol.MsgRelay, protocol.SubRelayNodeReq,
protocol.RelayNodeReq{PeerNode: app.PeerNode},
protocol.RelayNodeReq{PeerNode: app.PeerNode, Mode: mode},
protocol.MsgRelay, protocol.SubRelayNodeRsp,
10*time.Second,
)
@@ -465,6 +469,11 @@ func (c *Client) tryRelay(app config.AppConfig) {
var rsp protocol.RelayNodeRsp
protocol.DecodePayload(rspData, &rsp)
if rsp.Error != 0 {
if mode != "official" {
log.Printf("[client] no relay available for %s, fallback official", app.PeerNode)
go c.tryRelay(app, "official")
return
}
log.Printf("[client] no relay available for %s", app.PeerNode)
return
}
@@ -545,6 +554,19 @@ func (c *Client) reportConnect(app config.AppConfig, rc protocol.ReportConnect)
c.conn.Write(protocol.MsgReport, protocol.SubReportConnect, rc)
}
func (c *Client) writeSDWANConfig(cfg protocol.SDWANConfig) error {
path := c.sdwanPath
if path == "" {
path = "/etc/inp2p/sdwan.json"
}
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
_ = os.MkdirAll("/etc/inp2p", 0755)
return os.WriteFile(path, b, 0644)
}
func (c *Client) applySDWAN(cfg protocol.SDWANConfig) error {
selfIP := ""
for _, n := range cfg.Nodes {
@@ -578,11 +600,24 @@ func (c *Client) applySDWAN(cfg protocol.SDWANConfig) error {
// fallback broad route for hub mode / compatibility
_ = runCmd("ip", "route", "replace", pfx.String(), "dev", "optun")
// refresh rule/table 100 for sdwan
_ = runCmd("ip", "rule", "add", "pref", "100", "from", selfIP, "table", "100")
_ = runCmd("ip", "route", "replace", pfx.String(), "dev", "optun", "table", "100")
c.sdwanMu.Lock()
c.sdwan = cfg
c.sdwanIP = selfIP
c.sdwanMu.Unlock()
// persist sdwan config for local use/diagnostics
if err := c.writeSDWANConfig(cfg); err != nil {
log.Printf("[client] write sdwan.json failed: %v", err)
}
// Apply subnet proxy (if configured)
if err := c.applySubnetProxy(cfg); err != nil {
log.Printf("[client] applySubnetProxy failed: %v", err)
}
// Try to start TUN reader, but don't fail SDWAN apply if it errors
if err := c.ensureTUNReader(); err != nil {
log.Printf("[client] ensureTUNReader failed (non-fatal): %v", err)
@@ -591,6 +626,39 @@ func (c *Client) applySDWAN(cfg protocol.SDWANConfig) error {
return nil
}
// applySubnetProxy configures local subnet proxying based on SDWAN config.
func (c *Client) applySubnetProxy(cfg protocol.SDWANConfig) error {
if len(cfg.SubnetProxies) == 0 {
return nil
}
self := c.cfg.Node
for _, sp := range cfg.SubnetProxies {
if sp.Node != self {
// for non-proxy nodes, add route to virtualCIDR via proxy node IP
proxyIP := ""
for _, n := range cfg.Nodes {
if n.Node == sp.Node {
proxyIP = strings.TrimSpace(n.IP)
break
}
}
if proxyIP == "" {
continue
}
_ = runCmd("ip", "route", "replace", sp.VirtualCIDR, "via", proxyIP, "dev", "optun")
continue
}
// This node is the proxy
_ = runCmd("sysctl", "-w", "net.ipv4.ip_forward=1")
// map virtualCIDR -> localCIDR (NETMAP)
if sp.VirtualCIDR != "" && sp.LocalCIDR != "" {
_ = runCmd("iptables", "-t", "nat", "-A", "PREROUTING", "-d", sp.VirtualCIDR, "-j", "NETMAP", "--to", sp.LocalCIDR)
_ = runCmd("iptables", "-t", "nat", "-A", "POSTROUTING", "-s", sp.LocalCIDR, "-j", "MASQUERADE")
}
}
return nil
}
func (c *Client) ensureTUNReader() error {
c.tunMu.Lock()
defer c.tunMu.Unlock()
@@ -637,13 +705,13 @@ func (c *Client) tunReadLoop() {
if f == nil {
return
}
n, err := f.Read(buf)
n, err := unix.Read(int(f.Fd()), buf)
if err != nil {
if c.IsStopping() {
return
}
// Log only real errors, not EOF or timeout
if err.Error() != "EOF" && err.Error() != "resource temporarily unavailable" {
// Ignore transient errors
if err != unix.EINTR && err != unix.EAGAIN {
log.Printf("[client] tun read error: %v", err)
}
time.Sleep(100 * time.Millisecond)