mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-06 05:45:14 +03:00
Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes
This commit is contained in:
259
transport/sudoku/kip.go
Normal file
259
transport/sudoku/kip.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package sudoku
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sudokuobfs "github.com/sagernet/sing-box/transport/sudoku/obfs/sudoku"
|
||||
)
|
||||
|
||||
const (
|
||||
kipMagic = "kip"
|
||||
|
||||
KIPTypeClientHello byte = 0x01
|
||||
KIPTypeServerHello byte = 0x02
|
||||
|
||||
KIPTypeOpenTCP byte = 0x10
|
||||
KIPTypeStartMux byte = 0x11
|
||||
KIPTypeStartUoT byte = 0x12
|
||||
KIPTypeKeepAlive byte = 0x14
|
||||
)
|
||||
|
||||
// KIP feature bits are advisory capability flags negotiated during the handshake.
|
||||
// They represent control-plane message families.
|
||||
const (
|
||||
KIPFeatOpenTCP uint32 = 1 << 0
|
||||
KIPFeatMux uint32 = 1 << 1
|
||||
KIPFeatUoT uint32 = 1 << 2
|
||||
KIPFeatKeepAlive uint32 = 1 << 4
|
||||
|
||||
KIPFeatAll = KIPFeatOpenTCP | KIPFeatMux | KIPFeatUoT | KIPFeatKeepAlive
|
||||
)
|
||||
|
||||
const (
|
||||
kipHelloUserHashSize = 8
|
||||
kipHelloNonceSize = 16
|
||||
kipHelloPubSize = 32
|
||||
kipMaxPayload = 64 * 1024
|
||||
)
|
||||
|
||||
const kipClientHelloTableHintSize = 4
|
||||
|
||||
var errKIP = errors.New("kip protocol error")
|
||||
|
||||
type KIPMessage struct {
|
||||
Type byte
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func WriteKIPMessage(w io.Writer, typ byte, payload []byte) error {
|
||||
if w == nil {
|
||||
return fmt.Errorf("%w: nil writer", errKIP)
|
||||
}
|
||||
if len(payload) > kipMaxPayload {
|
||||
return fmt.Errorf("%w: payload too large: %d", errKIP, len(payload))
|
||||
}
|
||||
|
||||
var hdr [3 + 1 + 2]byte
|
||||
copy(hdr[:3], []byte(kipMagic))
|
||||
hdr[3] = typ
|
||||
binary.BigEndian.PutUint16(hdr[4:], uint16(len(payload)))
|
||||
|
||||
return writeAllChunks(w, hdr[:], payload)
|
||||
}
|
||||
|
||||
func ReadKIPMessage(r io.Reader) (*KIPMessage, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w: nil reader", errKIP)
|
||||
}
|
||||
var hdr [3 + 1 + 2]byte
|
||||
if _, err := io.ReadFull(r, hdr[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(hdr[:3]) != kipMagic {
|
||||
return nil, fmt.Errorf("%w: bad magic", errKIP)
|
||||
}
|
||||
typ := hdr[3]
|
||||
n := int(binary.BigEndian.Uint16(hdr[4:]))
|
||||
if n < 0 || n > kipMaxPayload {
|
||||
return nil, fmt.Errorf("%w: invalid payload length: %d", errKIP, n)
|
||||
}
|
||||
var payload []byte
|
||||
if n > 0 {
|
||||
payload = make([]byte, n)
|
||||
if _, err := io.ReadFull(r, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &KIPMessage{Type: typ, Payload: payload}, nil
|
||||
}
|
||||
|
||||
type KIPClientHello struct {
|
||||
Timestamp time.Time
|
||||
UserHash [kipHelloUserHashSize]byte
|
||||
Nonce [kipHelloNonceSize]byte
|
||||
ClientPub [kipHelloPubSize]byte
|
||||
Features uint32
|
||||
TableHint uint32
|
||||
HasTableHint bool
|
||||
}
|
||||
|
||||
type KIPServerHello struct {
|
||||
Nonce [kipHelloNonceSize]byte
|
||||
ServerPub [kipHelloPubSize]byte
|
||||
SelectedFeats uint32
|
||||
}
|
||||
|
||||
func newKIPClientHello(userHash [kipHelloUserHashSize]byte, nonce [kipHelloNonceSize]byte, clientPub [kipHelloPubSize]byte, feats uint32, tableHint uint32, hasTableHint bool) *KIPClientHello {
|
||||
return &KIPClientHello{
|
||||
Timestamp: time.Now(),
|
||||
UserHash: userHash,
|
||||
Nonce: nonce,
|
||||
ClientPub: clientPub,
|
||||
Features: feats,
|
||||
TableHint: tableHint,
|
||||
HasTableHint: hasTableHint,
|
||||
}
|
||||
}
|
||||
|
||||
func kipUserHashFromKey(psk string) [kipHelloUserHashSize]byte {
|
||||
var out [kipHelloUserHashSize]byte
|
||||
psk = strings.TrimSpace(psk)
|
||||
if psk == "" {
|
||||
return out
|
||||
}
|
||||
|
||||
// Align with upstream: when the client carries private key material (or even just a public key),
|
||||
// prefer hashing the raw hex bytes so different split/master keys can be distinguished.
|
||||
if keyBytes, err := hex.DecodeString(psk); err == nil && len(keyBytes) > 0 {
|
||||
sum := sha256.Sum256(keyBytes)
|
||||
copy(out[:], sum[:kipHelloUserHashSize])
|
||||
return out
|
||||
}
|
||||
|
||||
sum := sha256.Sum256([]byte(psk))
|
||||
copy(out[:], sum[:kipHelloUserHashSize])
|
||||
return out
|
||||
}
|
||||
|
||||
func KIPUserHashHexFromKey(psk string) string {
|
||||
uh := kipUserHashFromKey(psk)
|
||||
return hex.EncodeToString(uh[:])
|
||||
}
|
||||
|
||||
func (m *KIPClientHello) EncodePayload() []byte {
|
||||
var b bytes.Buffer
|
||||
var tmp [8]byte
|
||||
binary.BigEndian.PutUint64(tmp[:], uint64(m.Timestamp.Unix()))
|
||||
b.Write(tmp[:])
|
||||
b.Write(m.UserHash[:])
|
||||
b.Write(m.Nonce[:])
|
||||
b.Write(m.ClientPub[:])
|
||||
var f [4]byte
|
||||
binary.BigEndian.PutUint32(f[:], m.Features)
|
||||
b.Write(f[:])
|
||||
if m.HasTableHint {
|
||||
var hint [kipClientHelloTableHintSize]byte
|
||||
binary.BigEndian.PutUint32(hint[:], m.TableHint)
|
||||
b.Write(hint[:])
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func DecodeKIPClientHelloPayload(payload []byte) (*KIPClientHello, error) {
|
||||
const minLen = 8 + kipHelloUserHashSize + kipHelloNonceSize + kipHelloPubSize + 4
|
||||
if len(payload) < minLen {
|
||||
return nil, fmt.Errorf("%w: client hello too short", errKIP)
|
||||
}
|
||||
var h KIPClientHello
|
||||
ts := int64(binary.BigEndian.Uint64(payload[:8]))
|
||||
h.Timestamp = time.Unix(ts, 0)
|
||||
off := 8
|
||||
copy(h.UserHash[:], payload[off:off+kipHelloUserHashSize])
|
||||
off += kipHelloUserHashSize
|
||||
copy(h.Nonce[:], payload[off:off+kipHelloNonceSize])
|
||||
off += kipHelloNonceSize
|
||||
copy(h.ClientPub[:], payload[off:off+kipHelloPubSize])
|
||||
off += kipHelloPubSize
|
||||
h.Features = binary.BigEndian.Uint32(payload[off : off+4])
|
||||
off += 4
|
||||
if len(payload) >= off+kipClientHelloTableHintSize {
|
||||
h.TableHint = binary.BigEndian.Uint32(payload[off : off+kipClientHelloTableHintSize])
|
||||
h.HasTableHint = true
|
||||
}
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
func ResolveClientHelloTable(selected *sudokuobfs.Table, candidates []*sudokuobfs.Table, hello *KIPClientHello) (*sudokuobfs.Table, error) {
|
||||
if selected == nil {
|
||||
return nil, fmt.Errorf("nil selected table")
|
||||
}
|
||||
if hello == nil || !hello.HasTableHint {
|
||||
return selected, nil
|
||||
}
|
||||
if selected.Hint() == hello.TableHint {
|
||||
return selected, nil
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return nil, fmt.Errorf("no table candidates")
|
||||
}
|
||||
|
||||
var hinted *sudokuobfs.Table
|
||||
for _, candidate := range candidates {
|
||||
if candidate == nil || candidate.Hint() != hello.TableHint {
|
||||
continue
|
||||
}
|
||||
hinted = candidate
|
||||
break
|
||||
}
|
||||
if hinted == nil {
|
||||
return nil, fmt.Errorf("unknown table hint: %d", hello.TableHint)
|
||||
}
|
||||
if hinted != selected && (!hinted.IsASCII || !selected.IsASCII) {
|
||||
return nil, fmt.Errorf("table hint %d mismatches probed uplink table", hello.TableHint)
|
||||
}
|
||||
return hinted, nil
|
||||
}
|
||||
|
||||
func (m *KIPServerHello) EncodePayload() []byte {
|
||||
var b bytes.Buffer
|
||||
b.Write(m.Nonce[:])
|
||||
b.Write(m.ServerPub[:])
|
||||
var f [4]byte
|
||||
binary.BigEndian.PutUint32(f[:], m.SelectedFeats)
|
||||
b.Write(f[:])
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func DecodeKIPServerHelloPayload(payload []byte) (*KIPServerHello, error) {
|
||||
const want = kipHelloNonceSize + kipHelloPubSize + 4
|
||||
if len(payload) != want {
|
||||
return nil, fmt.Errorf("%w: server hello bad len: %d", errKIP, len(payload))
|
||||
}
|
||||
var h KIPServerHello
|
||||
off := 0
|
||||
copy(h.Nonce[:], payload[off:off+kipHelloNonceSize])
|
||||
off += kipHelloNonceSize
|
||||
copy(h.ServerPub[:], payload[off:off+kipHelloPubSize])
|
||||
off += kipHelloPubSize
|
||||
h.SelectedFeats = binary.BigEndian.Uint32(payload[off : off+4])
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
func writeFull(w io.Writer, b []byte) error {
|
||||
for len(b) > 0 {
|
||||
n, err := w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user