feat: INP2P v0.1.0 — complete P2P tunneling system
Core modules (M1-M6): - pkg/protocol: message format, encoding, NAT type enums - pkg/config: server/client config structs, env vars, validation - pkg/auth: CRC64 token, TOTP gen/verify, one-time relay tokens - pkg/nat: UDP/TCP STUN client and server - pkg/signal: WSS message dispatch, sync request/response - pkg/punch: UDP/TCP hole punching + priority chain - pkg/mux: stream multiplexer (7B frame: StreamID+Flags+Len) - pkg/tunnel: mux-based port forwarding with stats - pkg/relay: relay manager with TOTP auth + session bridging - internal/server: signaling server (login/heartbeat/report/coordinator) - internal/client: client (NAT detect/login/punch/relay/reconnect) - cmd/inp2ps + cmd/inp2pc: main entrypoints with graceful shutdown All tests pass: 16 tests across 5 packages Code: 3559 lines core + 861 lines tests = 19 source files
This commit is contained in:
276
pkg/protocol/protocol.go
Normal file
276
pkg/protocol/protocol.go
Normal file
@@ -0,0 +1,276 @@
|
||||
// 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
|
||||
)
|
||||
|
||||
// ─── 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
|
||||
}
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user