feat: raw binary SDWAN data plane + EncodeRaw + TUN close-on-stop
- protocol: add SubTunnelSDWANRaw subtype + EncodeRaw() for zero-copy IP packets - client: tunReadLoop sends raw frames (no JSON/base64 overhead) - client: SubTunnelSDWANRaw handler strips header and writes directly to TUN - client: Stop() closes TUN file FIRST to unblock tunReadLoop - server: SubTunnelSDWANRaw handler parses IPv4 src/dst from raw packet - server: RouteSDWANPacket forwards as raw frame to destination Verified: hcss(10.10.0.3) ↔ i-6986(10.10.0.2) ping 3/3, 0% loss, 46ms RTT
This commit is contained in:
@@ -289,6 +289,18 @@ func (c *Client) registerHandlers() {
|
|||||||
return c.writeTUN(pkt.Payload)
|
return c.writeTUN(pkt.Payload)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// SDWAN raw packet (binary payload) from server
|
||||||
|
c.conn.OnMessage(protocol.MsgTunnel, protocol.SubTunnelSDWANRaw, func(data []byte) error {
|
||||||
|
if len(data) <= protocol.HeaderSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
payload := data[protocol.HeaderSize:]
|
||||||
|
if len(payload) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.writeTUN(payload)
|
||||||
|
})
|
||||||
|
|
||||||
// Handle edit app push
|
// Handle edit app push
|
||||||
c.conn.OnMessage(protocol.MsgPush, protocol.SubPushEditApp, func(data []byte) error {
|
c.conn.OnMessage(protocol.MsgPush, protocol.SubPushEditApp, func(data []byte) error {
|
||||||
var app protocol.AppConfig
|
var app protocol.AppConfig
|
||||||
@@ -643,18 +655,15 @@ func (c *Client) tunReadLoop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dstIP := net.IP(pkt[16:20]).String()
|
dstIP := net.IP(pkt[16:20]).String()
|
||||||
srcIP := net.IP(pkt[12:16]).String()
|
|
||||||
c.sdwanMu.RLock()
|
c.sdwanMu.RLock()
|
||||||
self := c.sdwanIP
|
self := c.sdwanIP
|
||||||
c.sdwanMu.RUnlock()
|
c.sdwanMu.RUnlock()
|
||||||
if dstIP == self {
|
if dstIP == self {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_ = c.conn.Write(protocol.MsgTunnel, protocol.SubTunnelSDWANData, protocol.SDWANPacket{
|
// send raw binary to avoid JSON base64 overhead
|
||||||
SrcIP: srcIP,
|
frame := protocol.EncodeRaw(protocol.MsgTunnel, protocol.SubTunnelSDWANRaw, pkt)
|
||||||
DstIP: dstIP,
|
_ = c.conn.WriteRaw(frame)
|
||||||
Payload: append([]byte(nil), pkt...),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,6 +699,12 @@ func runCmd(name string, args ...string) error {
|
|||||||
// Stop shuts down the client.
|
// Stop shuts down the client.
|
||||||
func (c *Client) Stop() {
|
func (c *Client) Stop() {
|
||||||
close(c.quit)
|
close(c.quit)
|
||||||
|
c.tunMu.Lock()
|
||||||
|
if c.tunFile != nil {
|
||||||
|
_ = c.tunFile.Close()
|
||||||
|
c.tunFile = nil
|
||||||
|
}
|
||||||
|
c.tunMu.Unlock()
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
}
|
}
|
||||||
@@ -701,12 +716,6 @@ func (c *Client) Stop() {
|
|||||||
t.Close()
|
t.Close()
|
||||||
}
|
}
|
||||||
c.tMu.Unlock()
|
c.tMu.Unlock()
|
||||||
c.tunMu.Lock()
|
|
||||||
if c.tunFile != nil {
|
|
||||||
_ = c.tunFile.Close()
|
|
||||||
c.tunFile = nil
|
|
||||||
}
|
|
||||||
c.tunMu.Unlock()
|
|
||||||
c.wg.Wait()
|
c.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,5 +143,6 @@ func (s *Server) RouteSDWANPacket(from *NodeInfo, pkt protocol.SDWANPacket) {
|
|||||||
|
|
||||||
pkt.FromNode = from.Name
|
pkt.FromNode = from.Name
|
||||||
pkt.ToNode = toNode
|
pkt.ToNode = toNode
|
||||||
_ = to.Conn.Write(protocol.MsgTunnel, protocol.SubTunnelSDWANData, pkt)
|
frame := protocol.EncodeRaw(protocol.MsgTunnel, protocol.SubTunnelSDWANRaw, pkt.Payload)
|
||||||
|
_ = to.Conn.WriteRaw(frame)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ func (s *Server) registerHandlers(conn *signal.Conn, node *NodeInfo) {
|
|||||||
return s.handleRelayNodeReq(conn, node, req)
|
return s.handleRelayNodeReq(conn, node, req)
|
||||||
})
|
})
|
||||||
|
|
||||||
// SDWAN data plane packet relay (server as control-plane router)
|
// SDWAN data plane packet relay (JSON control payload)
|
||||||
conn.OnMessage(protocol.MsgTunnel, protocol.SubTunnelSDWANData, func(data []byte) error {
|
conn.OnMessage(protocol.MsgTunnel, protocol.SubTunnelSDWANData, func(data []byte) error {
|
||||||
var pkt protocol.SDWANPacket
|
var pkt protocol.SDWANPacket
|
||||||
if err := protocol.DecodePayload(data, &pkt); err != nil {
|
if err := protocol.DecodePayload(data, &pkt); err != nil {
|
||||||
@@ -318,6 +318,26 @@ func (s *Server) registerHandlers(conn *signal.Conn, node *NodeInfo) {
|
|||||||
s.RouteSDWANPacket(node, pkt)
|
s.RouteSDWANPacket(node, pkt)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// SDWAN data plane packet relay (raw IP payload)
|
||||||
|
conn.OnMessage(protocol.MsgTunnel, protocol.SubTunnelSDWANRaw, func(data []byte) error {
|
||||||
|
if len(data) <= protocol.HeaderSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
payload := data[protocol.HeaderSize:]
|
||||||
|
if len(payload) < 20 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
version := payload[0] >> 4
|
||||||
|
if version != 4 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
srcIP := net.IP(payload[12:16]).String()
|
||||||
|
dstIP := net.IP(payload[16:20]).String()
|
||||||
|
pkt := protocol.SDWANPacket{SrcIP: srcIP, DstIP: dstIP, Payload: payload}
|
||||||
|
s.RouteSDWANPacket(node, pkt)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRelayNodeReq finds and returns the best relay node.
|
// handleRelayNodeReq finds and returns the best relay node.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// Package protocol defines the INP2P wire protocol.
|
// Package protocol defines the INP2P wire protocol.
|
||||||
//
|
//
|
||||||
// Message format: [Header 8B] + [JSON payload]
|
// Message format: [Header 8B] + [JSON payload]
|
||||||
// Header: DataLen(uint32 LE) + MainType(uint16 LE) + SubType(uint16 LE)
|
//
|
||||||
// DataLen = len(header) + len(payload) = 8 + len(json)
|
// Header: DataLen(uint32 LE) + MainType(uint16 LE) + SubType(uint16 LE)
|
||||||
|
// DataLen = len(header) + len(payload) = 8 + len(json)
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -69,6 +70,7 @@ const (
|
|||||||
// Sub types: MsgTunnel
|
// Sub types: MsgTunnel
|
||||||
const (
|
const (
|
||||||
SubTunnelSDWANData uint16 = iota
|
SubTunnelSDWANData uint16 = iota
|
||||||
|
SubTunnelSDWANRaw
|
||||||
)
|
)
|
||||||
|
|
||||||
// ─── Sub types: MsgRelay ───
|
// ─── Sub types: MsgRelay ───
|
||||||
@@ -151,6 +153,20 @@ func Encode(mainType, subType uint16, payload interface{}) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeRaw packs header + raw (binary) payload.
|
||||||
|
func EncodeRaw(mainType, subType uint16, payload []byte) []byte {
|
||||||
|
h := Header{
|
||||||
|
DataLen: uint32(HeaderSize + len(payload)),
|
||||||
|
MainType: mainType,
|
||||||
|
SubType: subType,
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.Grow(int(h.DataLen))
|
||||||
|
_ = binary.Write(buf, binary.LittleEndian, h)
|
||||||
|
buf.Write(payload)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeHeader reads the 8-byte header from r.
|
// DecodeHeader reads the 8-byte header from r.
|
||||||
func DecodeHeader(data []byte) (Header, error) {
|
func DecodeHeader(data []byte) (Header, error) {
|
||||||
if len(data) < HeaderSize {
|
if len(data) < HeaderSize {
|
||||||
@@ -179,8 +195,8 @@ type LoginReq struct {
|
|||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
NATType NATType `json:"natType"`
|
NATType NATType `json:"natType"`
|
||||||
ShareBandwidth int `json:"shareBandwidth"`
|
ShareBandwidth int `json:"shareBandwidth"`
|
||||||
RelayEnabled bool `json:"relayEnabled"` // --relay flag
|
RelayEnabled bool `json:"relayEnabled"` // --relay flag
|
||||||
SuperRelay bool `json:"superRelay"` // --super flag
|
SuperRelay bool `json:"superRelay"` // --super flag
|
||||||
PublicIP string `json:"publicIP,omitempty"`
|
PublicIP string `json:"publicIP,omitempty"`
|
||||||
PublicPort int `json:"publicPort,omitempty"`
|
PublicPort int `json:"publicPort,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -216,7 +232,7 @@ type PunchParams struct {
|
|||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
NATType NATType `json:"natType"`
|
NATType NATType `json:"natType"`
|
||||||
Token uint64 `json:"token"` // TOTP for auth
|
Token uint64 `json:"token"` // TOTP for auth
|
||||||
IPv6 string `json:"ipv6,omitempty"`
|
IPv6 string `json:"ipv6,omitempty"`
|
||||||
HasIPv4 int `json:"hasIPv4"`
|
HasIPv4 int `json:"hasIPv4"`
|
||||||
LinkMode string `json:"linkMode"` // "udp" or "tcp"
|
LinkMode string `json:"linkMode"` // "udp" or "tcp"
|
||||||
@@ -279,7 +295,7 @@ type SDWANConfig struct {
|
|||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
GatewayCIDR string `json:"gatewayCIDR"`
|
GatewayCIDR string `json:"gatewayCIDR"`
|
||||||
Mode string `json:"mode,omitempty"` // hub | mesh | fullmesh
|
Mode string `json:"mode,omitempty"` // hub | mesh | fullmesh
|
||||||
IP string `json:"ip,omitempty"` // node self IP if pushed per-node
|
IP string `json:"ip,omitempty"` // node self IP if pushed per-node
|
||||||
MTU int `json:"mtu,omitempty"`
|
MTU int `json:"mtu,omitempty"`
|
||||||
Routes []string `json:"routes,omitempty"`
|
Routes []string `json:"routes,omitempty"`
|
||||||
Nodes []SDWANNode `json:"nodes"`
|
Nodes []SDWANNode `json:"nodes"`
|
||||||
@@ -302,17 +318,17 @@ type SDWANPacket struct {
|
|||||||
|
|
||||||
// ReportConnect is the connection result reported to server.
|
// ReportConnect is the connection result reported to server.
|
||||||
type ReportConnect struct {
|
type ReportConnect struct {
|
||||||
PeerNode string `json:"peerNode"`
|
PeerNode string `json:"peerNode"`
|
||||||
NATType NATType `json:"natType"`
|
NATType NATType `json:"natType"`
|
||||||
PeerNATType NATType `json:"peerNatType"`
|
PeerNATType NATType `json:"peerNatType"`
|
||||||
LinkMode string `json:"linkMode"` // "udppunch", "tcppunch", "relay"
|
LinkMode string `json:"linkMode"` // "udppunch", "tcppunch", "relay"
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
RTT int `json:"rtt,omitempty"` // milliseconds
|
RTT int `json:"rtt,omitempty"` // milliseconds
|
||||||
RelayNode string `json:"relayNode,omitempty"`
|
RelayNode string `json:"relayNode,omitempty"`
|
||||||
Protocol string `json:"protocol,omitempty"`
|
Protocol string `json:"protocol,omitempty"`
|
||||||
SrcPort int `json:"srcPort,omitempty"`
|
SrcPort int `json:"srcPort,omitempty"`
|
||||||
DstPort int `json:"dstPort,omitempty"`
|
DstPort int `json:"dstPort,omitempty"`
|
||||||
DstHost string `json:"dstHost,omitempty"`
|
DstHost string `json:"dstHost,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
ShareBandwidth int `json:"shareBandWidth,omitempty"`
|
ShareBandwidth int `json:"shareBandWidth,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user