mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-19 08:04:38 +03:00
Protocol Basics.
This commit is contained in:
@@ -23,7 +23,7 @@ type Client struct {
|
|||||||
cfg config.Config
|
cfg config.Config
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
clientSessionKey string
|
clientSessionKey string
|
||||||
streams *StreamStore
|
socksConnections *SOCKSConnectionStore
|
||||||
|
|
||||||
connMu sync.Mutex
|
connMu sync.Mutex
|
||||||
conns map[net.Conn]struct{}
|
conns map[net.Conn]struct{}
|
||||||
@@ -36,7 +36,7 @@ func New(cfg config.Config, lg *logger.Logger) *Client {
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: lg,
|
log: lg,
|
||||||
clientSessionKey: clientSessionKey,
|
clientSessionKey: clientSessionKey,
|
||||||
streams: NewStreamStore(),
|
socksConnections: NewSOCKSConnectionStore(),
|
||||||
conns: make(map[net.Conn]struct{}),
|
conns: make(map[net.Conn]struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -13,7 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Stream struct {
|
type SOCKSConnection struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
ClientSessionKey string
|
ClientSessionKey string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@@ -24,35 +24,39 @@ type Stream struct {
|
|||||||
TargetAddressType byte
|
TargetAddressType byte
|
||||||
InitialPayload []byte
|
InitialPayload []byte
|
||||||
BufferedBytes int
|
BufferedBytes int
|
||||||
|
NextSequence uint64
|
||||||
SOCKSAuthMethod byte
|
SOCKSAuthMethod byte
|
||||||
SOCKSUsername string
|
SOCKSUsername string
|
||||||
HandshakeDone bool
|
HandshakeDone bool
|
||||||
ConnectAccepted bool
|
ConnectAccepted bool
|
||||||
|
CloseReadSent bool
|
||||||
|
CloseWriteSent bool
|
||||||
|
ResetSent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) InitialPayloadHex() string {
|
func (s *SOCKSConnection) InitialPayloadHex() string {
|
||||||
if len(s.InitialPayload) == 0 {
|
if len(s.InitialPayload) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return hex.EncodeToString(s.InitialPayload)
|
return hex.EncodeToString(s.InitialPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
type StreamStore struct {
|
type SOCKSConnectionStore struct {
|
||||||
nextID atomic.Uint64
|
nextID atomic.Uint64
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
items map[uint64]*Stream
|
items map[uint64]*SOCKSConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamStore() *StreamStore {
|
func NewSOCKSConnectionStore() *SOCKSConnectionStore {
|
||||||
return &StreamStore{
|
return &SOCKSConnectionStore{
|
||||||
items: make(map[uint64]*Stream),
|
items: make(map[uint64]*SOCKSConnection),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StreamStore) New(clientSessionKey string, clientAddress string) *Stream {
|
func (s *SOCKSConnectionStore) New(clientSessionKey string, clientAddress string) *SOCKSConnection {
|
||||||
id := s.nextID.Add(1)
|
id := s.nextID.Add(1)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
stream := &Stream{
|
socksConn := &SOCKSConnection{
|
||||||
ID: id,
|
ID: id,
|
||||||
ClientSessionKey: clientSessionKey,
|
ClientSessionKey: clientSessionKey,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
@@ -61,12 +65,12 @@ func (s *StreamStore) New(clientSessionKey string, clientAddress string) *Stream
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.items[id] = stream
|
s.items[id] = socksConn
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return stream
|
return socksConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StreamStore) Delete(id uint64) {
|
func (s *SOCKSConnectionStore) Delete(id uint64) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
delete(s.items, id)
|
delete(s.items, id)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|||||||
+32
-32
@@ -46,21 +46,21 @@ func (c *Client) handleConn(ctx context.Context, conn net.Conn) {
|
|||||||
defer c.unregisterConn(conn)
|
defer c.unregisterConn(conn)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
stream := c.streams.New(c.clientSessionKey, conn.RemoteAddr().String())
|
socksConn := c.socksConnections.New(c.clientSessionKey, conn.RemoteAddr().String())
|
||||||
defer c.streams.Delete(stream.ID)
|
defer c.socksConnections.Delete(socksConn.ID)
|
||||||
|
|
||||||
c.log.Infof(
|
c.log.Infof(
|
||||||
"<green>accepted client <cyan>%s</cyan> stream=<cyan>%d</cyan> client_session_key=<cyan>%s</cyan></green>",
|
"<green>accepted client <cyan>%s</cyan> socks_id=<cyan>%d</cyan> client_session_key=<cyan>%s</cyan></green>",
|
||||||
conn.RemoteAddr(), stream.ID, stream.ClientSessionKey,
|
conn.RemoteAddr(), socksConn.ID, socksConn.ClientSessionKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := c.handleSOCKS5(ctx, conn, stream); err != nil {
|
if err := c.handleSOCKS5(ctx, conn, socksConn); err != nil {
|
||||||
c.log.Errorf("<red>stream=<cyan>%d</cyan> closed: <cyan>%v</cyan></red>", stream.ID, err)
|
c.log.Errorf("<red>socks_id=<cyan>%d</cyan> closed: <cyan>%v</cyan></red>", socksConn.ID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) handleSOCKS5(ctx context.Context, conn net.Conn, stream *Stream) error {
|
func (c *Client) handleSOCKS5(ctx context.Context, conn net.Conn, socksConn *SOCKSConnection) error {
|
||||||
version := make([]byte, 1)
|
version := make([]byte, 1)
|
||||||
if _, err := io.ReadFull(conn, version); err != nil {
|
if _, err := io.ReadFull(conn, version); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -69,13 +69,13 @@ func (c *Client) handleSOCKS5(ctx context.Context, conn net.Conn, stream *Stream
|
|||||||
return fmt.Errorf("<red>unsupported SOCKS version: <cyan>%d</cyan></red>", version[0])
|
return fmt.Errorf("<red>unsupported SOCKS version: <cyan>%d</cyan></red>", version[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
method, err := c.negotiateAuth(conn, stream)
|
method, err := c.negotiateAuth(conn, socksConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == socksMethodUserPass {
|
if method == socksMethodUserPass {
|
||||||
if err := c.handleUserPassAuth(conn, stream); err != nil {
|
if err := c.handleUserPassAuth(conn, socksConn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,26 +85,26 @@ func (c *Client) handleSOCKS5(ctx context.Context, conn net.Conn, stream *Stream
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.TargetHost = targetHost
|
socksConn.TargetHost = targetHost
|
||||||
stream.TargetPort = targetPort
|
socksConn.TargetPort = targetPort
|
||||||
stream.TargetAddressType = atyp
|
socksConn.TargetAddressType = atyp
|
||||||
stream.ConnectAccepted = true
|
socksConn.ConnectAccepted = true
|
||||||
stream.HandshakeDone = true
|
socksConn.HandshakeDone = true
|
||||||
stream.LastActivityAt = time.Now()
|
socksConn.LastActivityAt = time.Now()
|
||||||
|
|
||||||
if err := writeSocksReply(conn, socksReplySuccess); err != nil {
|
if err := writeSocksReply(conn, socksReplySuccess); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.log.Infof(
|
c.log.Infof(
|
||||||
"<green>stream=<cyan>%d</cyan> CONNECT target=<cyan>%s:%d</cyan> auth_method=<cyan>%d</cyan> client_session_key=<cyan>%s</cyan></green>",
|
"<green>socks_id=<cyan>%d</cyan> CONNECT target=<cyan>%s:%d</cyan> auth_method=<cyan>%d</cyan> client_session_key=<cyan>%s</cyan></green>",
|
||||||
stream.ID, stream.TargetHost, stream.TargetPort, stream.SOCKSAuthMethod, stream.ClientSessionKey,
|
socksConn.ID, socksConn.TargetHost, socksConn.TargetPort, socksConn.SOCKSAuthMethod, socksConn.ClientSessionKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
return c.captureInitialPayload(ctx, conn, stream)
|
return c.captureInitialPayload(ctx, conn, socksConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) negotiateAuth(conn net.Conn, stream *Stream) (byte, error) {
|
func (c *Client) negotiateAuth(conn net.Conn, socksConn *SOCKSConnection) (byte, error) {
|
||||||
countBuf := make([]byte, 1)
|
countBuf := make([]byte, 1)
|
||||||
if _, err := io.ReadFull(conn, countBuf); err != nil {
|
if _, err := io.ReadFull(conn, countBuf); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -134,11 +134,11 @@ func (c *Client) negotiateAuth(conn net.Conn, stream *Stream) (byte, error) {
|
|||||||
return 0, errors.New("no acceptable auth method")
|
return 0, errors.New("no acceptable auth method")
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.SOCKSAuthMethod = selected
|
socksConn.SOCKSAuthMethod = selected
|
||||||
return selected, nil
|
return selected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) handleUserPassAuth(conn net.Conn, stream *Stream) error {
|
func (c *Client) handleUserPassAuth(conn net.Conn, socksConn *SOCKSConnection) error {
|
||||||
header := make([]byte, 2)
|
header := make([]byte, 2)
|
||||||
if _, err := io.ReadFull(conn, header); err != nil {
|
if _, err := io.ReadFull(conn, header); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -163,7 +163,7 @@ func (c *Client) handleUserPassAuth(conn net.Conn, stream *Stream) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ok := string(username) == c.cfg.SOCKSUsername && string(password) == c.cfg.SOCKSPassword
|
ok := string(username) == c.cfg.SOCKSUsername && string(password) == c.cfg.SOCKSPassword
|
||||||
stream.SOCKSUsername = string(username)
|
socksConn.SOCKSUsername = string(username)
|
||||||
if ok {
|
if ok {
|
||||||
_, err := conn.Write([]byte{socksUserPassVersion, socksAuthSuccess})
|
_, err := conn.Write([]byte{socksUserPassVersion, socksAuthSuccess})
|
||||||
return err
|
return err
|
||||||
@@ -247,7 +247,7 @@ func writeSocksReply(conn net.Conn, reply byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) captureInitialPayload(ctx context.Context, conn net.Conn, stream *Stream) error {
|
func (c *Client) captureInitialPayload(ctx context.Context, conn net.Conn, socksConn *SOCKSConnection) error {
|
||||||
peekTimeout := 2 * time.Second
|
peekTimeout := 2 * time.Second
|
||||||
idleTimeout := 30 * time.Second
|
idleTimeout := 30 * time.Second
|
||||||
buf := make([]byte, 32*1024)
|
buf := make([]byte, 32*1024)
|
||||||
@@ -258,12 +258,12 @@ func (c *Client) captureInitialPayload(ctx context.Context, conn net.Conn, strea
|
|||||||
|
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err == nil && n > 0 {
|
if err == nil && n > 0 {
|
||||||
stream.InitialPayload = append([]byte(nil), buf[:n]...)
|
socksConn.InitialPayload = append([]byte(nil), buf[:n]...)
|
||||||
stream.BufferedBytes += n
|
socksConn.BufferedBytes += n
|
||||||
stream.LastActivityAt = time.Now()
|
socksConn.LastActivityAt = time.Now()
|
||||||
c.log.Debugf(
|
c.log.Debugf(
|
||||||
"<green>stream=<cyan>%d</cyan> captured initial payload bytes=<cyan>%d</cyan> target=<cyan>%s</cyan> client_session_key=<cyan>%s</cyan></green>",
|
"<green>socks_id=<cyan>%d</cyan> captured initial payload bytes=<cyan>%d</cyan> target=<cyan>%s</cyan> client_session_key=<cyan>%s</cyan></green>",
|
||||||
stream.ID, n, net.JoinHostPort(stream.TargetHost, strconv.Itoa(int(stream.TargetPort))), stream.ClientSessionKey,
|
socksConn.ID, n, net.JoinHostPort(socksConn.TargetHost, strconv.Itoa(int(socksConn.TargetPort))), socksConn.ClientSessionKey,
|
||||||
)
|
)
|
||||||
} else if ne, ok := err.(net.Error); !ok || !ne.Timeout() {
|
} else if ne, ok := err.(net.Error); !ok || !ne.Timeout() {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
@@ -287,11 +287,11 @@ func (c *Client) captureInitialPayload(ctx context.Context, conn net.Conn, strea
|
|||||||
|
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
stream.BufferedBytes += n
|
socksConn.BufferedBytes += n
|
||||||
stream.LastActivityAt = time.Now()
|
socksConn.LastActivityAt = time.Now()
|
||||||
c.log.Debugf(
|
c.log.Debugf(
|
||||||
"<green>stream=<cyan>%d</cyan> buffered payload chunk=<cyan>%d</cyan> total=<cyan>%d</cyan> client_session_key=<cyan>%s</cyan></green>",
|
"<green>socks_id=<cyan>%d</cyan> buffered payload chunk=<cyan>%d</cyan> total=<cyan>%d</cyan> client_session_key=<cyan>%s</cyan></green>",
|
||||||
stream.ID, n, stream.BufferedBytes, stream.ClientSessionKey,
|
socksConn.ID, n, socksConn.BufferedBytes, socksConn.ClientSessionKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// ==============================================================================
|
||||||
|
// MasterHttpRelayVPN
|
||||||
|
// Author: MasterkinG32
|
||||||
|
// Github: https://github.com/masterking32
|
||||||
|
// Year: 2026
|
||||||
|
// ==============================================================================
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "masterhttprelayvpn/internal/protocol"
|
||||||
|
|
||||||
|
func (s *SOCKSConnection) nextSequence() uint64 {
|
||||||
|
s.NextSequence++
|
||||||
|
return s.NextSequence
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SOCKSConnection) BuildSOCKSConnectPacket() protocol.Packet {
|
||||||
|
packet := protocol.NewPacket(s.ClientSessionKey, protocol.PacketTypeSOCKSConnect)
|
||||||
|
packet.SOCKSID = s.ID
|
||||||
|
packet.Target = &protocol.Target{
|
||||||
|
Host: s.TargetHost,
|
||||||
|
Port: s.TargetPort,
|
||||||
|
AddressType: s.TargetAddressType,
|
||||||
|
}
|
||||||
|
if len(s.InitialPayload) > 0 {
|
||||||
|
packet.Payload = append([]byte(nil), s.InitialPayload...)
|
||||||
|
}
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SOCKSConnection) BuildSOCKSDataPacket(payload []byte, final bool) protocol.Packet {
|
||||||
|
packet := protocol.NewPacket(s.ClientSessionKey, protocol.PacketTypeSOCKSData)
|
||||||
|
packet.SOCKSID = s.ID
|
||||||
|
packet.Sequence = s.nextSequence()
|
||||||
|
packet.Final = final
|
||||||
|
if len(payload) > 0 {
|
||||||
|
packet.Payload = append([]byte(nil), payload...)
|
||||||
|
}
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SOCKSConnection) BuildSOCKSCloseReadPacket() protocol.Packet {
|
||||||
|
s.CloseReadSent = true
|
||||||
|
|
||||||
|
packet := protocol.NewPacket(s.ClientSessionKey, protocol.PacketTypeSOCKSCloseRead)
|
||||||
|
packet.SOCKSID = s.ID
|
||||||
|
packet.Sequence = s.nextSequence()
|
||||||
|
packet.Final = true
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SOCKSConnection) BuildSOCKSCloseWritePacket() protocol.Packet {
|
||||||
|
s.CloseWriteSent = true
|
||||||
|
|
||||||
|
packet := protocol.NewPacket(s.ClientSessionKey, protocol.PacketTypeSOCKSCloseWrite)
|
||||||
|
packet.SOCKSID = s.ID
|
||||||
|
packet.Sequence = s.nextSequence()
|
||||||
|
packet.Final = true
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SOCKSConnection) BuildSOCKSRSTPacket() protocol.Packet {
|
||||||
|
s.ResetSent = true
|
||||||
|
|
||||||
|
packet := protocol.NewPacket(s.ClientSessionKey, protocol.PacketTypeSOCKSRST)
|
||||||
|
packet.SOCKSID = s.ID
|
||||||
|
packet.Sequence = s.nextSequence()
|
||||||
|
packet.Final = true
|
||||||
|
return packet
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
// ==============================================================================
|
||||||
|
// MasterHttpRelayVPN
|
||||||
|
// Author: MasterkinG32
|
||||||
|
// Github: https://github.com/masterking32
|
||||||
|
// Year: 2026
|
||||||
|
// ==============================================================================
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CurrentVersion = 1
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidPacketType = errors.New("invalid packet type")
|
||||||
|
ErrMissingClientKey = errors.New("missing client session key")
|
||||||
|
ErrMissingSOCKSID = errors.New("missing socks id")
|
||||||
|
ErrMissingSequence = errors.New("missing sequence number")
|
||||||
|
ErrPayloadNotAllowed = errors.New("payload not allowed for packet type")
|
||||||
|
ErrInvalidTargetPort = errors.New("invalid target port")
|
||||||
|
ErrInvalidTargetHost = errors.New("invalid target host")
|
||||||
|
ErrTargetNotAllowed = errors.New("target not allowed for packet type")
|
||||||
|
ErrMissingBatchID = errors.New("missing batch id")
|
||||||
|
ErrEmptyBatch = errors.New("empty batch")
|
||||||
|
ErrMixedClientKeyBatch = errors.New("batch contains multiple client session keys")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Target struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
AddressType byte `json:"address_type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Target) Address() string {
|
||||||
|
if t.Host == "" || t.Port == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(t.Host, strconv.Itoa(int(t.Port)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Target) Validate() error {
|
||||||
|
if strings.TrimSpace(t.Host) == "" {
|
||||||
|
return ErrInvalidTargetHost
|
||||||
|
}
|
||||||
|
if t.Port == 0 {
|
||||||
|
return ErrInvalidTargetPort
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
Version int `json:"v"`
|
||||||
|
ClientSessionKey string `json:"client_session_key"`
|
||||||
|
SOCKSID uint64 `json:"socks_id,omitempty"`
|
||||||
|
Type PacketType `json:"type"`
|
||||||
|
Sequence uint64 `json:"seq,omitempty"`
|
||||||
|
FragmentID uint32 `json:"fragment_id,omitempty"`
|
||||||
|
TotalFragments uint32 `json:"total_fragments,omitempty"`
|
||||||
|
Final bool `json:"final,omitempty"`
|
||||||
|
CreatedAtUnixMS int64 `json:"created_at_unix_ms"`
|
||||||
|
Target *Target `json:"target,omitempty"`
|
||||||
|
Payload []byte `json:"payload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacket(clientSessionKey string, packetType PacketType) Packet {
|
||||||
|
return Packet{
|
||||||
|
Version: CurrentVersion,
|
||||||
|
ClientSessionKey: clientSessionKey,
|
||||||
|
Type: packetType,
|
||||||
|
CreatedAtUnixMS: time.Now().UnixMilli(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Packet) Validate() error {
|
||||||
|
if p.Version <= 0 {
|
||||||
|
return fmt.Errorf("invalid packet version: %d", p.Version)
|
||||||
|
}
|
||||||
|
if !p.Type.Valid() {
|
||||||
|
return ErrInvalidPacketType
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(p.ClientSessionKey) == "" {
|
||||||
|
return ErrMissingClientKey
|
||||||
|
}
|
||||||
|
if PacketTypeNeedsStreamID(p.Type) && p.SOCKSID == 0 {
|
||||||
|
return ErrMissingSOCKSID
|
||||||
|
}
|
||||||
|
if PacketTypeNeedsSequence(p.Type) && p.Sequence == 0 {
|
||||||
|
return ErrMissingSequence
|
||||||
|
}
|
||||||
|
if !PacketTypeAllowsPayload(p.Type) && len(p.Payload) > 0 {
|
||||||
|
return ErrPayloadNotAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.Type {
|
||||||
|
case PacketTypeSOCKSConnect:
|
||||||
|
if p.Target == nil {
|
||||||
|
return ErrTargetNotAllowed
|
||||||
|
}
|
||||||
|
if err := p.Target.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case PacketTypeSOCKSData,
|
||||||
|
PacketTypeSOCKSDataAck,
|
||||||
|
PacketTypeSOCKSCloseRead,
|
||||||
|
PacketTypeSOCKSCloseWrite,
|
||||||
|
PacketTypeSOCKSRST,
|
||||||
|
PacketTypeSOCKSConnectAck,
|
||||||
|
PacketTypeSOCKSConnectFail,
|
||||||
|
PacketTypeSOCKSRuleSetDenied,
|
||||||
|
PacketTypeSOCKSNetworkUnreachable,
|
||||||
|
PacketTypeSOCKSHostUnreachable,
|
||||||
|
PacketTypeSOCKSConnectionRefused,
|
||||||
|
PacketTypeSOCKSTTLExpired,
|
||||||
|
PacketTypeSOCKSCommandUnsupported,
|
||||||
|
PacketTypeSOCKSAddressTypeUnsupported,
|
||||||
|
PacketTypeSOCKSAuthFailed,
|
||||||
|
PacketTypeSOCKSUpstreamUnavailable:
|
||||||
|
if p.Target != nil {
|
||||||
|
return ErrTargetNotAllowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.TotalFragments > 0 && p.FragmentID >= p.TotalFragments {
|
||||||
|
return fmt.Errorf("invalid fragment range: id=%d total=%d", p.FragmentID, p.TotalFragments)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Packet) IdentityKey() string {
|
||||||
|
return fmt.Sprintf("%s:%d:%s:%d:%d", p.ClientSessionKey, p.SOCKSID, p.Type, p.Sequence, p.FragmentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PacketIdentityKey(clientSessionKey string, socksID uint64, packetType PacketType, sequence uint64, fragmentID uint32) string {
|
||||||
|
switch packetType {
|
||||||
|
case PacketTypeSOCKSData, PacketTypeSOCKSDataAck:
|
||||||
|
return fmt.Sprintf("%s:%d:%s:%d:%d", clientSessionKey, socksID, packetType, sequence, fragmentID)
|
||||||
|
case PacketTypeSOCKSCloseRead,
|
||||||
|
PacketTypeSOCKSCloseWrite,
|
||||||
|
PacketTypeSOCKSRST,
|
||||||
|
PacketTypeSOCKSConnect,
|
||||||
|
PacketTypeSOCKSConnectAck,
|
||||||
|
PacketTypeSOCKSConnectFail,
|
||||||
|
PacketTypeSOCKSRuleSetDenied,
|
||||||
|
PacketTypeSOCKSNetworkUnreachable,
|
||||||
|
PacketTypeSOCKSHostUnreachable,
|
||||||
|
PacketTypeSOCKSConnectionRefused,
|
||||||
|
PacketTypeSOCKSTTLExpired,
|
||||||
|
PacketTypeSOCKSCommandUnsupported,
|
||||||
|
PacketTypeSOCKSAddressTypeUnsupported,
|
||||||
|
PacketTypeSOCKSAuthFailed,
|
||||||
|
PacketTypeSOCKSUpstreamUnavailable:
|
||||||
|
return fmt.Sprintf("%s:%d:%s:%d", clientSessionKey, socksID, packetType, sequence)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s:%d:%s", clientSessionKey, socksID, packetType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Batch struct {
|
||||||
|
Version int `json:"v"`
|
||||||
|
BatchID string `json:"batch_id"`
|
||||||
|
ClientSessionKey string `json:"client_session_key"`
|
||||||
|
CreatedAtUnixMS int64 `json:"created_at_unix_ms"`
|
||||||
|
Packets []Packet `json:"packets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBatch(clientSessionKey string, batchID string, packets []Packet) Batch {
|
||||||
|
return Batch{
|
||||||
|
Version: CurrentVersion,
|
||||||
|
BatchID: batchID,
|
||||||
|
ClientSessionKey: clientSessionKey,
|
||||||
|
CreatedAtUnixMS: time.Now().UnixMilli(),
|
||||||
|
Packets: packets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBatchID() string {
|
||||||
|
now := time.Now().UTC().Format("20060102T150405.000000000Z")
|
||||||
|
random := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(random); err != nil {
|
||||||
|
return fmt.Sprintf("%s_batch", now)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s_%s", now, hex.EncodeToString(random))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Batch) Validate() error {
|
||||||
|
if b.Version <= 0 {
|
||||||
|
return fmt.Errorf("invalid batch version: %d", b.Version)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(b.BatchID) == "" {
|
||||||
|
return ErrMissingBatchID
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(b.ClientSessionKey) == "" {
|
||||||
|
return ErrMissingClientKey
|
||||||
|
}
|
||||||
|
if len(b.Packets) == 0 {
|
||||||
|
return ErrEmptyBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, packet := range b.Packets {
|
||||||
|
if err := packet.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if packet.ClientSessionKey != b.ClientSessionKey {
|
||||||
|
return ErrMixedClientKeyBatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// ==============================================================================
|
||||||
|
// MasterHttpRelayVPN
|
||||||
|
// Author: MasterkinG32
|
||||||
|
// Github: https://github.com/masterking32
|
||||||
|
// Year: 2026
|
||||||
|
// ==============================================================================
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
type PacketType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PacketTypeSOCKSConnect PacketType = "socks_connect"
|
||||||
|
PacketTypeSOCKSConnectAck PacketType = "socks_connect_ack"
|
||||||
|
PacketTypeSOCKSConnectFail PacketType = "socks_connect_fail"
|
||||||
|
PacketTypeSOCKSRuleSetDenied PacketType = "socks_ruleset_denied"
|
||||||
|
PacketTypeSOCKSNetworkUnreachable PacketType = "socks_network_unreachable"
|
||||||
|
PacketTypeSOCKSHostUnreachable PacketType = "socks_host_unreachable"
|
||||||
|
PacketTypeSOCKSConnectionRefused PacketType = "socks_connection_refused"
|
||||||
|
PacketTypeSOCKSTTLExpired PacketType = "socks_ttl_expired"
|
||||||
|
PacketTypeSOCKSCommandUnsupported PacketType = "socks_command_unsupported"
|
||||||
|
PacketTypeSOCKSAddressTypeUnsupported PacketType = "socks_address_type_unsupported"
|
||||||
|
PacketTypeSOCKSAuthFailed PacketType = "socks_auth_failed"
|
||||||
|
PacketTypeSOCKSUpstreamUnavailable PacketType = "socks_upstream_unavailable"
|
||||||
|
PacketTypeSOCKSData PacketType = "socks_data"
|
||||||
|
PacketTypeSOCKSDataAck PacketType = "socks_data_ack"
|
||||||
|
PacketTypeSOCKSCloseRead PacketType = "socks_close_read"
|
||||||
|
PacketTypeSOCKSCloseWrite PacketType = "socks_close_write"
|
||||||
|
PacketTypeSOCKSRST PacketType = "socks_rst"
|
||||||
|
PacketTypePing PacketType = "ping"
|
||||||
|
PacketTypePong PacketType = "pong"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p PacketType) String() string {
|
||||||
|
return string(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PacketType) Valid() bool {
|
||||||
|
switch p {
|
||||||
|
case PacketTypeSOCKSConnect,
|
||||||
|
PacketTypeSOCKSConnectAck,
|
||||||
|
PacketTypeSOCKSConnectFail,
|
||||||
|
PacketTypeSOCKSRuleSetDenied,
|
||||||
|
PacketTypeSOCKSNetworkUnreachable,
|
||||||
|
PacketTypeSOCKSHostUnreachable,
|
||||||
|
PacketTypeSOCKSConnectionRefused,
|
||||||
|
PacketTypeSOCKSTTLExpired,
|
||||||
|
PacketTypeSOCKSCommandUnsupported,
|
||||||
|
PacketTypeSOCKSAddressTypeUnsupported,
|
||||||
|
PacketTypeSOCKSAuthFailed,
|
||||||
|
PacketTypeSOCKSUpstreamUnavailable,
|
||||||
|
PacketTypeSOCKSData,
|
||||||
|
PacketTypeSOCKSDataAck,
|
||||||
|
PacketTypeSOCKSCloseRead,
|
||||||
|
PacketTypeSOCKSCloseWrite,
|
||||||
|
PacketTypeSOCKSRST,
|
||||||
|
PacketTypePing,
|
||||||
|
PacketTypePong:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PacketTypeNeedsStreamID(packetType PacketType) bool {
|
||||||
|
switch packetType {
|
||||||
|
case PacketTypePing, PacketTypePong:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return packetType.Valid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PacketTypeNeedsSequence(packetType PacketType) bool {
|
||||||
|
switch packetType {
|
||||||
|
case PacketTypeSOCKSData,
|
||||||
|
PacketTypeSOCKSDataAck,
|
||||||
|
PacketTypeSOCKSCloseRead,
|
||||||
|
PacketTypeSOCKSCloseWrite,
|
||||||
|
PacketTypeSOCKSRST:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PacketTypeAllowsPayload(packetType PacketType) bool {
|
||||||
|
switch packetType {
|
||||||
|
case PacketTypeSOCKSConnect,
|
||||||
|
PacketTypeSOCKSConnectFail,
|
||||||
|
PacketTypeSOCKSRuleSetDenied,
|
||||||
|
PacketTypeSOCKSNetworkUnreachable,
|
||||||
|
PacketTypeSOCKSHostUnreachable,
|
||||||
|
PacketTypeSOCKSConnectionRefused,
|
||||||
|
PacketTypeSOCKSTTLExpired,
|
||||||
|
PacketTypeSOCKSCommandUnsupported,
|
||||||
|
PacketTypeSOCKSAddressTypeUnsupported,
|
||||||
|
PacketTypeSOCKSAuthFailed,
|
||||||
|
PacketTypeSOCKSUpstreamUnavailable,
|
||||||
|
PacketTypeSOCKSData,
|
||||||
|
PacketTypePing,
|
||||||
|
PacketTypePong:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user