添加vmess的客户端代码

This commit is contained in:
e1732a364fed
2022-05-14 09:27:11 +08:00
parent fa50ff60b4
commit c2d842cb23
5 changed files with 586 additions and 1 deletions

View File

@@ -14,7 +14,6 @@ import (
//tcp就不测了我们实践直接测试完全好使这里重点测试UDP
// 因为chrome也是无法通过 socks5去申请udp链接的所以没法自己用浏览器测试
//下面的部分代码在 main.go 中也有用到.
func TestUDP(t *testing.T) {
s := socks5.NewServer()

136
proxy/vmess/aead.go Normal file
View File

@@ -0,0 +1,136 @@
package vmess
import (
"bytes"
"crypto/cipher"
"encoding/binary"
"io"
)
type aeadWriter struct {
io.Writer
cipher.AEAD
nonce []byte
buf []byte
count uint16
iv []byte
}
// AEADWriter returns a aead writer
func AEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) io.Writer {
return &aeadWriter{
Writer: w,
AEAD: aead,
buf: make([]byte, lenSize+chunkSize),
nonce: make([]byte, aead.NonceSize()),
count: 0,
iv: iv,
}
}
func (w *aeadWriter) Write(b []byte) (int, error) {
n, err := w.ReadFrom(bytes.NewBuffer(b))
return int(n), err
}
func (w *aeadWriter) ReadFrom(r io.Reader) (n int64, err error) {
for {
buf := w.buf
payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()]
nr, er := r.Read(payloadBuf)
if nr > 0 {
n += int64(nr)
buf = buf[:lenSize+nr+w.Overhead()]
payloadBuf = payloadBuf[:nr]
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nr+w.Overhead()))
binary.BigEndian.PutUint16(w.nonce[:2], w.count)
copy(w.nonce[2:], w.iv[2:12])
w.Seal(payloadBuf[:0], w.nonce, payloadBuf, nil)
w.count++
_, ew := w.Writer.Write(buf)
if ew != nil {
err = ew
break
}
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
err = er
}
break
}
}
return n, err
}
type aeadReader struct {
io.Reader
cipher.AEAD
nonce []byte
buf []byte
leftover []byte
count uint16
iv []byte
}
// AEADReader returns a aead reader
func AEADReader(r io.Reader, aead cipher.AEAD, iv []byte) io.Reader {
return &aeadReader{
Reader: r,
AEAD: aead,
buf: make([]byte, lenSize+chunkSize),
nonce: make([]byte, aead.NonceSize()),
count: 0,
iv: iv,
}
}
func (r *aeadReader) Read(b []byte) (int, error) {
if len(r.leftover) > 0 {
n := copy(b, r.leftover)
r.leftover = r.leftover[n:]
return n, nil
}
// get length
_, err := io.ReadFull(r.Reader, r.buf[:lenSize])
if err != nil {
return 0, err
}
// if length == 0, then this is the end
l := binary.BigEndian.Uint16(r.buf[:lenSize])
if l == 0 {
return 0, nil
}
// get payload
buf := r.buf[:l]
_, err = io.ReadFull(r.Reader, buf)
if err != nil {
return 0, err
}
binary.BigEndian.PutUint16(r.nonce[:2], r.count)
copy(r.nonce[2:], r.iv[2:12])
_, err = r.Open(buf[:0], r.nonce, buf, nil)
r.count++
if err != nil {
return 0, err
}
dataLen := int(l) - r.Overhead()
m := copy(b, r.buf[:dataLen])
if m < int(dataLen) {
r.leftover = r.buf[m:dataLen]
}
return m, err
}

90
proxy/vmess/chunk.go Normal file
View File

@@ -0,0 +1,90 @@
package vmess
import (
"encoding/binary"
"io"
"github.com/e1732a364fed/v2ray_simple/utils"
)
const (
lenSize = 2
chunkSize = 1 << 14 // 16384
)
type chunkedWriter struct {
io.Writer
}
// ChunkedWriter returns a chunked writer
func ChunkedWriter(w io.Writer) io.Writer {
return &chunkedWriter{Writer: w}
}
func (w *chunkedWriter) Write(b []byte) (n int, err error) {
buf := utils.GetBytes(lenSize + chunkSize)
defer utils.PutBytes(buf)
left := len(b)
for left != 0 {
writeLen := left
if writeLen > chunkSize {
writeLen = chunkSize
}
copy(buf[lenSize:], b[n:n+writeLen])
binary.BigEndian.PutUint16(buf[:lenSize], uint16(writeLen))
_, err = w.Writer.Write(buf[:lenSize+writeLen])
if err != nil {
break
}
n += writeLen
left -= writeLen
}
return
}
type chunkedReader struct {
io.Reader
left int
}
// ChunkedReader returns a chunked reader
func ChunkedReader(r io.Reader) io.Reader {
return &chunkedReader{Reader: r}
}
func (r *chunkedReader) Read(b []byte) (int, error) {
if r.left == 0 {
// get length
buf := utils.GetBytes(lenSize)
_, err := io.ReadFull(r.Reader, buf[:lenSize])
if err != nil {
return 0, err
}
r.left = int(binary.BigEndian.Uint16(buf[:lenSize]))
utils.PutBytes(buf)
// if left == 0, then this is the end
if r.left == 0 {
return 0, nil
}
}
readLen := len(b)
if readLen > r.left {
readLen = r.left
}
n, err := r.Reader.Read(b[:readLen])
if err != nil {
return 0, err
}
r.left -= n
return n, err
}

303
proxy/vmess/client.go Normal file
View File

@@ -0,0 +1,303 @@
package vmess
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/md5"
"encoding/binary"
"errors"
"hash/fnv"
"io"
"math/rand"
"net"
"net/url"
"strings"
"time"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/proxy"
"github.com/e1732a364fed/v2ray_simple/utils"
"golang.org/x/crypto/chacha20poly1305"
)
func init() {
//proxy.RegisterClient(Name, NewVmessClient)
}
func NewVmessClient(url *url.URL) (*Client, error) {
addr := url.Host
uuidStr := url.User.Username()
uuid, err := utils.StrToUUID(uuidStr)
if err != nil {
return nil, err
}
query := url.Query()
security := query.Get("security")
if security == "" {
security = "none"
}
c := &Client{}
c.SetAddrStr(addr)
user := utils.V2rayUser(uuid)
c.user = user
//c.users = append(c.users, user.GenAlterIDUsers(int(alterID))...)
c.opt = OptChunkStream
security = strings.ToLower(security)
switch security {
case "aes-128-gcm":
c.security = SecurityAES128GCM
case "chacha20-poly1305":
c.security = SecurityChacha20Poly1305
case "none":
c.security = SecurityNone
case "":
// NOTE: use basic format when no method specified
c.opt = OptBasicFormat
c.security = SecurityNone
default:
return nil, errors.New("unknown security type: " + security)
}
rand.Seed(time.Now().UnixNano())
return c, nil
}
// Client is a vmess client
type Client struct {
proxy.Base
user utils.V2rayUser
opt byte
security byte
}
func (c *Client) Name() string { return Name }
func (c *Client) Handshake(underlay net.Conn, firstPayload []byte, target netLayer.Addr) (io.ReadWriter, error) {
conn := &ClientConn{user: c.user, opt: c.opt, security: c.security}
conn.Conn = underlay
conn.addr, conn.atyp = target.AddressBytes()
conn.port = uint16(target.Port)
randBytes := utils.GetBytes(32)
rand.Read(randBytes)
copy(conn.reqBodyIV[:], randBytes[:16])
copy(conn.reqBodyKey[:], randBytes[16:32])
utils.PutBytes(randBytes)
conn.reqRespV = byte(rand.Intn(1 << 8))
conn.respBodyIV = md5.Sum(conn.reqBodyIV[:])
conn.respBodyKey = md5.Sum(conn.reqBodyKey[:])
// Auth
err := conn.Auth()
if err != nil {
return nil, err
}
// Request
err = conn.handshake()
if err != nil {
return nil, err
}
return conn, nil
}
// ClientConn is a connection to vmess server
type ClientConn struct {
user utils.V2rayUser
opt byte
security byte
atyp byte
addr []byte
port uint16
reqBodyIV [16]byte
reqBodyKey [16]byte
reqRespV byte
respBodyIV [16]byte
respBodyKey [16]byte
net.Conn
dataReader io.Reader
dataWriter io.Writer
}
// Auth send auth info: HMAC("md5", UUID, UTC)
func (c *ClientConn) Auth() error {
ts := utils.GetBytes(8)
defer utils.PutBytes(ts)
binary.BigEndian.PutUint64(ts, uint64(time.Now().UTC().Unix()))
h := hmac.New(md5.New, c.user.IdentityBytes())
h.Write(ts)
_, err := c.Conn.Write(h.Sum(nil))
return err
}
// handshake sends request to server.
func (c *ClientConn) handshake() error {
buf := utils.GetBuf()
defer utils.PutBuf(buf)
// Request
buf.WriteByte(1) // Ver
buf.Write(c.reqBodyIV[:]) // IV
buf.Write(c.reqBodyKey[:]) // Key
buf.WriteByte(c.reqRespV) // V
buf.WriteByte(c.opt) // Opt
// pLen and Sec
paddingLen := rand.Intn(16)
pSec := byte(paddingLen<<4) | c.security // P(4bit) and Sec(4bit)
buf.WriteByte(pSec)
buf.WriteByte(0) // reserved
buf.WriteByte(CmdTCP) // cmd
// target
err := binary.Write(buf, binary.BigEndian, c.port) // port
if err != nil {
return err
}
buf.WriteByte(c.atyp) // atyp
buf.Write(c.addr) // addr
// padding
if paddingLen > 0 {
padding := utils.GetBytes(paddingLen)
rand.Read(padding)
buf.Write(padding)
utils.PutBytes(padding)
}
// F
fnv1a := fnv.New32a()
_, err = fnv1a.Write(buf.Bytes())
if err != nil {
return err
}
buf.Write(fnv1a.Sum(nil))
// log.Printf("Request Send %v", buf.Bytes())
block, err := aes.NewCipher(GetKey(c.user))
if err != nil {
return err
}
stream := cipher.NewCFBEncrypter(block, TimestampHash(time.Now().UTC().Unix()))
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
_, err = c.Conn.Write(buf.Bytes())
return err
}
// DecodeRespHeader decodes response header.
func (c *ClientConn) DecodeRespHeader() error {
block, err := aes.NewCipher(c.respBodyKey[:])
if err != nil {
return err
}
stream := cipher.NewCFBDecrypter(block, c.respBodyIV[:])
b := utils.GetBytes(4)
defer utils.PutBytes(b)
_, err = io.ReadFull(c.Conn, b)
if err != nil {
return err
}
stream.XORKeyStream(b, b)
if b[0] != c.reqRespV {
return errors.New("unexpected response header")
}
if b[2] != 0 {
// dataLen := int32(buf[3])
return errors.New("dynamic port is not supported now")
}
return nil
}
func (c *ClientConn) Write(b []byte) (n int, err error) {
if c.dataWriter != nil {
return c.dataWriter.Write(b)
}
c.dataWriter = c.Conn
if c.opt&OptChunkStream == OptChunkStream {
switch c.security {
case SecurityNone:
c.dataWriter = ChunkedWriter(c.Conn)
case SecurityAES128GCM:
block, _ := aes.NewCipher(c.reqBodyKey[:])
aead, _ := cipher.NewGCM(block)
c.dataWriter = AEADWriter(c.Conn, aead, c.reqBodyIV[:])
case SecurityChacha20Poly1305:
key := utils.GetBytes(32)
t := md5.Sum(c.reqBodyKey[:])
copy(key, t[:])
t = md5.Sum(key[:16])
copy(key[16:], t[:])
aead, _ := chacha20poly1305.New(key)
c.dataWriter = AEADWriter(c.Conn, aead, c.reqBodyIV[:])
utils.PutBytes(key)
}
}
return c.dataWriter.Write(b)
}
func (c *ClientConn) Read(b []byte) (n int, err error) {
if c.dataReader != nil {
return c.dataReader.Read(b)
}
err = c.DecodeRespHeader()
if err != nil {
return 0, err
}
c.dataReader = c.Conn
if c.opt&OptChunkStream == OptChunkStream {
switch c.security {
case SecurityNone:
c.dataReader = ChunkedReader(c.Conn)
case SecurityAES128GCM:
block, _ := aes.NewCipher(c.respBodyKey[:])
aead, _ := cipher.NewGCM(block)
c.dataReader = AEADReader(c.Conn, aead, c.respBodyIV[:])
case SecurityChacha20Poly1305:
key := utils.GetBytes(32)
t := md5.Sum(c.respBodyKey[:])
copy(key, t[:])
t = md5.Sum(key[:16])
copy(key[16:], t[:])
aead, _ := chacha20poly1305.New(key)
c.dataReader = AEADReader(c.Conn, aead, c.respBodyIV[:])
utils.PutBytes(key)
}
}
return c.dataReader.Read(b)
}

57
proxy/vmess/vmess.go Normal file
View File

@@ -0,0 +1,57 @@
/*Package vmess implements vmess client.
from github.com/Dreamacro/clash/tree/master/transport/vmess/
*/
package vmess
import (
"crypto/md5"
"encoding/binary"
"github.com/e1732a364fed/v2ray_simple/utils"
)
const Name = "vmess"
// Request Options
const (
OptBasicFormat byte = 0 // 不加密传输
OptChunkStream byte = 1 // 分块传输每个分块使用如下Security方法加密
)
// Security types
const (
SecurityAES128GCM byte = 3
SecurityChacha20Poly1305 byte = 4
SecurityNone byte = 5
)
//v2ray CMD types
const (
CmdTCP byte = 1
CmdUDP byte = 2
)
// GetKey returns the key of AES-128-CFB encrypter
// KeyMD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
func GetKey(uuid [16]byte) []byte {
md5hash := md5.New()
md5hash.Write(uuid[:])
md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
return md5hash.Sum(nil)
}
// TimestampHash returns the iv of AES-128-CFB encrypter
// IVMD5(X + X + X + X)X = []byte(timestamp.now) (8 bytes, Big Endian)
func TimestampHash(unixSec int64) []byte {
ts := utils.GetBytes(8)
defer utils.PutBytes(ts)
binary.BigEndian.PutUint64(ts, uint64(unixSec))
md5hash := md5.New()
md5hash.Write(ts)
md5hash.Write(ts)
md5hash.Write(ts)
md5hash.Write(ts)
return md5hash.Sum(nil)
}