mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-12-24 13:27:56 +08:00
添加vmess的客户端代码
This commit is contained in:
@@ -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
136
proxy/vmess/aead.go
Normal 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
90
proxy/vmess/chunk.go
Normal 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
303
proxy/vmess/client.go
Normal 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
57
proxy/vmess/vmess.go
Normal 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
|
||||
// Key:MD5(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
|
||||
// IV:MD5(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)
|
||||
}
|
||||
Reference in New Issue
Block a user