mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 17:35:40 +08:00
215 lines
4.1 KiB
Go
215 lines
4.1 KiB
Go
package vmess
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"crypto/hmac"
|
||
"crypto/md5"
|
||
"encoding/binary"
|
||
"errors"
|
||
"hash/fnv"
|
||
"io"
|
||
"math/rand"
|
||
"net"
|
||
"time"
|
||
)
|
||
|
||
// Request Options
|
||
const (
|
||
OptChunkStream byte = 1
|
||
OptReuseTCPConnection byte = 2
|
||
OptMetadataObfuscate byte = 4
|
||
)
|
||
|
||
// SEC types
|
||
const (
|
||
SecTypeUnknown byte = 0
|
||
SecTypeLegacy byte = 1
|
||
SecTypeAuto byte = 2
|
||
SecTypeAES128GCM byte = 3
|
||
SecTypeChacha20Poly1305 byte = 4
|
||
SecTypeNone byte = 5
|
||
)
|
||
|
||
// CMD types
|
||
const (
|
||
CmdTCP byte = 1
|
||
CmdUDP byte = 2
|
||
)
|
||
|
||
// Client vmess client
|
||
type Client struct {
|
||
user *User
|
||
atype AType
|
||
addr Addr
|
||
port Port
|
||
|
||
reqBodyIV [16]byte
|
||
reqBodyKey [16]byte
|
||
reqRespV byte
|
||
respBodyKey [16]byte
|
||
respBodyIV [16]byte
|
||
|
||
net.Conn
|
||
connected bool
|
||
}
|
||
|
||
// NewClient .
|
||
func NewClient(uuid, target string) (*Client, error) {
|
||
user, err := NewUser(uuid)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
c := &Client{user: user}
|
||
|
||
c.atype, c.addr, c.port, err = ParseAddr(target)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
randBytes := make([]byte, 33)
|
||
rand.Read(randBytes)
|
||
|
||
copy(c.reqBodyIV[:], randBytes[:16])
|
||
copy(c.reqBodyKey[:], randBytes[16:32])
|
||
c.reqRespV = randBytes[32]
|
||
|
||
c.respBodyIV = md5.Sum(c.reqBodyIV[:])
|
||
c.respBodyKey = md5.Sum(c.reqBodyKey[:])
|
||
|
||
return c, nil
|
||
}
|
||
|
||
// EncodeAuthInfo returns HMAC("md5", UUID, UTC) result
|
||
func (c *Client) EncodeAuthInfo() []byte {
|
||
ts := make([]byte, 8)
|
||
binary.BigEndian.PutUint64(ts, uint64(time.Now().UTC().Unix()))
|
||
|
||
h := hmac.New(md5.New, c.user.UUID[:])
|
||
h.Write(ts)
|
||
|
||
return h.Sum(nil)
|
||
}
|
||
|
||
// EncodeRequest encodes requests to network bytes
|
||
func (c *Client) EncodeRequest() ([]byte, error) {
|
||
buf := new(bytes.Buffer)
|
||
|
||
// Request
|
||
buf.WriteByte(1) // Ver
|
||
buf.Write(c.reqBodyIV[:]) // IV
|
||
buf.Write(c.reqBodyKey[:]) // Key
|
||
buf.WriteByte(c.reqRespV) // V
|
||
buf.WriteByte(0) // Opt
|
||
|
||
// pLen and Sec
|
||
paddingLen := rand.Intn(16)
|
||
pSec := byte(paddingLen<<4) | SecTypeNone // P(4bit) and Sec(4bit)
|
||
buf.WriteByte(pSec)
|
||
|
||
buf.WriteByte(0) // reserved
|
||
buf.WriteByte(CmdTCP) // cmd
|
||
|
||
// target
|
||
binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
|
||
buf.WriteByte(byte(c.atype)) // atype
|
||
buf.Write(c.addr) // addr
|
||
|
||
// padding
|
||
if paddingLen > 0 {
|
||
padding := make([]byte, paddingLen)
|
||
rand.Read(padding)
|
||
buf.Write(padding)
|
||
}
|
||
|
||
// F
|
||
fnv1a := fnv.New32a()
|
||
fnv1a.Write(buf.Bytes())
|
||
buf.Write(fnv1a.Sum(nil))
|
||
|
||
// AES-128-CFB crypt the request:
|
||
// Key:MD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
|
||
// IV:MD5(X + X + X + X),X = []byte(timestamp.now) (8 bytes, Big Endian)
|
||
md5hash := md5.New()
|
||
md5hash.Write(c.user.UUID[:])
|
||
md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
|
||
key := md5hash.Sum(nil)
|
||
|
||
md5hash.Reset()
|
||
ts := make([]byte, 8)
|
||
binary.BigEndian.PutUint64(ts, uint64(time.Now().UTC().Unix()))
|
||
md5hash.Write(ts)
|
||
md5hash.Write(ts)
|
||
md5hash.Write(ts)
|
||
md5hash.Write(ts)
|
||
iv := md5hash.Sum(nil)
|
||
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
stream := cipher.NewCFBEncrypter(block, iv)
|
||
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
||
|
||
return buf.Bytes(), nil
|
||
}
|
||
|
||
// DecodeRespHeader .
|
||
func (c *Client) DecodeRespHeader() error {
|
||
block, err := aes.NewCipher(c.respBodyKey[:])
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
stream := cipher.NewCFBDecrypter(block, c.respBodyIV[:])
|
||
buf := make([]byte, 4)
|
||
io.ReadFull(c.Conn, buf)
|
||
stream.XORKeyStream(buf, buf)
|
||
|
||
if buf[0] != c.reqRespV {
|
||
return errors.New("unexpected response header")
|
||
}
|
||
|
||
// TODO: Dynamic port supported
|
||
// if buf[2] != 0 {
|
||
// cmd := buf[2]
|
||
// dataLen := int32(buf[3])
|
||
// }
|
||
|
||
c.connected = true
|
||
return nil
|
||
|
||
}
|
||
|
||
// NewConn wraps a net.Conn to VMessConn
|
||
func (c *Client) NewConn(rc net.Conn) (net.Conn, error) {
|
||
// AuthInfo
|
||
rc.Write(c.EncodeAuthInfo())
|
||
|
||
// Request
|
||
req, err := c.EncodeRequest()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
rc.Write(req)
|
||
|
||
c.Conn = rc
|
||
|
||
return c, err
|
||
}
|
||
|
||
func (c *Client) Read(b []byte) (n int, err error) {
|
||
if !c.connected {
|
||
c.DecodeRespHeader()
|
||
}
|
||
|
||
return c.Conn.Read(b)
|
||
}
|
||
|
||
func (c *Client) Write(b []byte) (n int, err error) {
|
||
return c.Conn.Write(b)
|
||
}
|