// Package protocol defines the INP2P wire protocol. // // Message format: [Header 8B] + [JSON payload] // // Header: DataLen(uint32 LE) + MainType(uint16 LE) + SubType(uint16 LE) // DataLen = len(header) + len(payload) = 8 + len(json) package protocol import ( "bytes" "encoding/binary" "encoding/json" "fmt" "io" ) // HeaderSize is the fixed 8-byte message header. const HeaderSize = 8 // ─── Main message types ─── const ( MsgLogin uint16 = 1 MsgHeartbeat uint16 = 2 MsgNAT uint16 = 3 MsgPush uint16 = 4 // signaling push (punch/relay coordination) MsgRelay uint16 = 5 MsgReport uint16 = 6 MsgTunnel uint16 = 7 // in-tunnel control messages ) // ─── Sub types: MsgLogin ─── const ( SubLoginReq uint16 = iota SubLoginRsp ) // ─── Sub types: MsgHeartbeat ─── const ( SubHeartbeatPing uint16 = iota SubHeartbeatPong ) // ─── Sub types: MsgNAT ─── const ( SubNATDetectReq uint16 = iota SubNATDetectRsp ) // ─── Sub types: MsgPush ─── const ( SubPushConnectReq uint16 = iota // "please connect to peer X" SubPushConnectRsp // peer's punch parameters SubPushPunchStart // coordinate simultaneous punch SubPushPunchResult // report punch outcome SubPushRelayOffer // relay node offers to relay SubPushNodeOnline // notify: destination came online SubPushEditApp // add/edit tunnel app SubPushDeleteApp // delete tunnel app SubPushReportApps // request app list SubPushSDWANConfig // push sdwan config to client SubPushSDWANPeer // push sdwan peer online/update SubPushSDWANDel // push sdwan peer offline/delete SubPushConfig // generic remote config push ) // Sub types: MsgTunnel const ( SubTunnelSDWANData uint16 = iota SubTunnelSDWANRaw ) // ─── Sub types: MsgRelay ─── const ( SubRelayNodeReq uint16 = iota SubRelayNodeRsp SubRelayDataReq // establish data channel through relay SubRelayDataRsp ) // ─── Sub types: MsgReport ─── const ( SubReportBasic uint16 = iota // OS, version, MAC, etc. SubReportApps // running tunnels SubReportConnect // connection result ) // ─── NAT types ─── type NATType int const ( NATNone NATType = 0 // public IP, no NAT NATCone NATType = 1 // full/restricted/port-restricted cone NATSymmetric NATType = 2 // symmetric (port changes per dest) NATUnknown NATType = 314 // detection failed / UDP blocked ) func (n NATType) String() string { switch n { case NATNone: return "None" case NATCone: return "Cone" case NATSymmetric: return "Symmetric" default: return "Unknown" } } // CanPunch returns true if at least one side is Cone (or has public IP). func CanPunch(a, b NATType) bool { return a == NATNone || b == NATNone || a == NATCone || b == NATCone } // ─── Header ─── type Header struct { DataLen uint32 MainType uint16 SubType uint16 } // ─── Encode / Decode ─── // Encode packs header + JSON payload into a byte slice. func Encode(mainType, subType uint16, payload interface{}) ([]byte, error) { var jsonData []byte if payload != nil { var err error jsonData, err = json.Marshal(payload) if err != nil { return nil, fmt.Errorf("marshal payload: %w", err) } } h := Header{ DataLen: uint32(HeaderSize + len(jsonData)), MainType: mainType, SubType: subType, } buf := new(bytes.Buffer) buf.Grow(int(h.DataLen)) if err := binary.Write(buf, binary.LittleEndian, h); err != nil { return nil, err } buf.Write(jsonData) 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. func DecodeHeader(data []byte) (Header, error) { if len(data) < HeaderSize { return Header{}, io.ErrShortBuffer } var h Header err := binary.Read(bytes.NewReader(data[:HeaderSize]), binary.LittleEndian, &h) return h, err } // DecodePayload unmarshals the JSON portion after the header. func DecodePayload(data []byte, v interface{}) error { if len(data) <= HeaderSize { return nil // empty payload is valid } return json.Unmarshal(data[HeaderSize:], v) } // ─── Common message structs ─── // LoginReq is sent by client on WSS connect. type LoginReq struct { Node string `json:"node"` Token uint64 `json:"token"` NodeSecret string `json:"nodeSecret,omitempty"` User string `json:"user,omitempty"` Version string `json:"version"` NATType NATType `json:"natType"` ShareBandwidth int `json:"shareBandwidth"` RelayEnabled bool `json:"relayEnabled"` // --relay flag SuperRelay bool `json:"superRelay"` // --super flag PublicIP string `json:"publicIP,omitempty"` PublicPort int `json:"publicPort,omitempty"` } type LoginRsp struct { Error int `json:"error"` Detail string `json:"detail,omitempty"` Ts int64 `json:"ts"` Token uint64 `json:"token"` User string `json:"user"` Node string `json:"node"` } // ReportBasic is the initial system info report after login. type ReportBasic struct { OS string `json:"os"` Mac string `json:"mac"` LanIP string `json:"lanIP"` Version string `json:"version"` HasIPv4 int `json:"hasIPv4"` HasUPNPorNATPMP int `json:"hasUPNPorNATPMP"` IPv6 string `json:"IPv6,omitempty"` PublicIP string `json:"publicIP,omitempty"` PublicPort int `json:"publicPort,omitempty"` } type ReportBasicRsp struct { Error int `json:"error"` } // PunchParams carries the information needed for hole-punching. type PunchParams struct { IP string `json:"ip"` Port int `json:"port"` NATType NATType `json:"natType"` Token uint64 `json:"token"` // TOTP for auth IPv6 string `json:"ipv6,omitempty"` HasIPv4 int `json:"hasIPv4"` LinkMode string `json:"linkMode"` // "udp" or "tcp" } // ConnectReq is pushed by server to coordinate a connection. type ConnectReq struct { From string `json:"from"` To string `json:"to"` FromIP string `json:"fromIP"` Peer PunchParams `json:"peer"` AppName string `json:"appName,omitempty"` Protocol string `json:"protocol"` // "tcp" or "udp" SrcPort int `json:"srcPort"` DstHost string `json:"dstHost"` DstPort int `json:"dstPort"` } type ConnectRsp struct { Error int `json:"error"` Detail string `json:"detail,omitempty"` From string `json:"from"` To string `json:"to"` Peer PunchParams `json:"peer,omitempty"` } // RelayNodeReq asks the server for a relay node. type RelayNodeReq struct { PeerNode string `json:"peerNode"` } type RelayNodeRsp struct { RelayName string `json:"relayName"` RelayIP string `json:"relayIP"` RelayPort int `json:"relayPort"` RelayToken uint64 `json:"relayToken"` Mode string `json:"mode"` // "private", "super", "server" Error int `json:"error"` } // AppConfig defines a tunnel application. type AppConfig struct { AppName string `json:"appName"` Protocol string `json:"protocol"` // "tcp" or "udp" SrcPort int `json:"srcPort"` PeerNode string `json:"peerNode"` DstHost string `json:"dstHost"` DstPort int `json:"dstPort"` Enabled int `json:"enabled"` RelayNode string `json:"relayNode,omitempty"` // force specific relay } type SDWANNode struct { Node string `json:"node"` IP string `json:"ip"` } type SDWANConfig struct { Enabled bool `json:"enabled,omitempty"` Name string `json:"name,omitempty"` GatewayCIDR string `json:"gatewayCIDR"` Mode string `json:"mode,omitempty"` // hub | mesh | fullmesh HubNode string `json:"hubNode,omitempty"` IP string `json:"ip,omitempty"` // node self IP if pushed per-node MTU int `json:"mtu,omitempty"` Routes []string `json:"routes,omitempty"` Nodes []SDWANNode `json:"nodes"` UpdatedAt int64 `json:"updatedAt,omitempty"` } type SDWANPeer struct { Node string `json:"node"` IP string `json:"ip"` Online bool `json:"online"` } type SDWANPacket struct { FromNode string `json:"fromNode,omitempty"` ToNode string `json:"toNode,omitempty"` SrcIP string `json:"srcIP,omitempty"` DstIP string `json:"dstIP,omitempty"` Payload []byte `json:"payload"` } // ReportConnect is the connection result reported to server. type ReportConnect struct { PeerNode string `json:"peerNode"` NATType NATType `json:"natType"` PeerNATType NATType `json:"peerNatType"` LinkMode string `json:"linkMode"` // "udppunch", "tcppunch", "relay" Error string `json:"error,omitempty"` RTT int `json:"rtt,omitempty"` // milliseconds RelayNode string `json:"relayNode,omitempty"` Protocol string `json:"protocol,omitempty"` SrcPort int `json:"srcPort,omitempty"` DstPort int `json:"dstPort,omitempty"` DstHost string `json:"dstHost,omitempty"` Version string `json:"version,omitempty"` ShareBandwidth int `json:"shareBandWidth,omitempty"` }