glider/proxy/vmess/client.go
2018-07-03 00:31:43 +08:00

215 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
// KeyMD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
// IVMD5(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)
}