修订代码;粘连vmess的请求包以及首包

发现clash的代码似乎没有粘包发送,而是会分包发送,则会造成明显流量特征。

我们粘着发送,就没有 握手包的特征了.

服务端的响应包也同理处理。
This commit is contained in:
e1732a364fed
2000-01-01 00:00:00 +00:00
parent 45ccfee83f
commit 8ea496dbfb
9 changed files with 241 additions and 223 deletions

View File

@@ -547,3 +547,89 @@ func UDPAddr2AddrPort(ua *net.UDPAddr) netip.AddrPort {
a, _ := netip.AddrFromSlice(ua.IP)
return netip.AddrPortFrom(a, uint16(ua.Port))
}
//依照 vmess/vless 协议的格式 依次读取 地址的 port, 域名/ip 信息
func V2rayGetAddrFrom(buf utils.ByteReader) (addr Addr, err error) {
pb1, err := buf.ReadByte()
if err != nil {
return
}
pb2, err := buf.ReadByte()
if err != nil {
return
}
port := uint16(pb1)<<8 + uint16(pb2)
if port == 0 {
err = utils.ErrInvalidData
return
}
addr.Port = int(port)
var b1 byte
b1, err = buf.ReadByte()
if err != nil {
return
}
switch b1 {
case AtypDomain:
var b2 byte
b2, err = buf.ReadByte()
if err != nil {
return
}
if b2 == 0 {
err = errors.New("got ATypDomain but domain lenth is marked to be 0")
return
}
bs := utils.GetBytes(int(b2))
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != int(b2) {
err = utils.ErrShortRead
return
}
addr.Name = string(bs[:n])
case AtypIP4:
bs := make([]byte, 4)
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != 4 {
err = utils.ErrShortRead
return
}
addr.IP = bs
case AtypIP6:
bs := make([]byte, net.IPv6len)
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != 4 {
err = utils.ErrShortRead
return
}
addr.IP = bs
default:
err = utils.ErrInvalidData
return
}
return
}

View File

@@ -98,7 +98,7 @@ func (*Server) CanFallback() bool {
func (s *Server) Name() string { return Name }
// 返回的bytes.Buffer 是用于 回落使用的,内含了整个读取的数据;不回落时不要使用该Buffer
func (s *Server) Handshake(underlay net.Conn) (result net.Conn, msgConn netLayer.MsgConn, targetAddr netLayer.Addr, returnErr error) {
func (s *Server) Handshake(underlay net.Conn) (tcpConn net.Conn, msgConn netLayer.MsgConn, targetAddr netLayer.Addr, returnErr error) {
if err := proxy.SetCommonReadTimeout(underlay); err != nil {
returnErr = err
@@ -225,7 +225,7 @@ realPart:
case CmdTCP, CmdUDP:
targetAddr, err = GetAddrFrom(readbuf)
targetAddr, err = netLayer.V2rayGetAddrFrom(readbuf)
if err != nil {
returnErr = utils.ErrInErr{ErrDesc: "fallback, reason 4", ErrDetail: err}

View File

@@ -196,7 +196,7 @@ func (u *UDPConn) ReadMsgFrom() ([]byte, netLayer.Addr, error) {
bs, err := u.readData_with_len()
return bs, u.raddr, err
case 1:
raddr, err := GetAddrFrom(u.bufr)
raddr, err := netLayer.V2rayGetAddrFrom(u.bufr)
if err != nil {
return nil, raddr, err
}
@@ -209,7 +209,7 @@ func (u *UDPConn) ReadMsgFrom() ([]byte, netLayer.Addr, error) {
return bs, u.raddr, err
}
} else {
raddr, err := GetAddrFrom(u.bufr)
raddr, err := netLayer.V2rayGetAddrFrom(u.bufr)
if err != nil {
return nil, raddr, err
}

View File

@@ -2,8 +2,6 @@
package vless
import (
"errors"
"net"
"net/url"
"strconv"
"strings"
@@ -27,92 +25,6 @@ const (
CmdMux
)
//依照 vless 协议的格式 依次读取 地址的 port, 域名/ip 信息
func GetAddrFrom(buf utils.ByteReader) (addr netLayer.Addr, err error) {
pb1, err := buf.ReadByte()
if err != nil {
return
}
pb2, err := buf.ReadByte()
if err != nil {
return
}
port := uint16(pb1)<<8 + uint16(pb2)
if port == 0 {
err = utils.ErrInvalidData
return
}
addr.Port = int(port)
var b1 byte
b1, err = buf.ReadByte()
if err != nil {
return
}
switch b1 {
case netLayer.AtypDomain:
var b2 byte
b2, err = buf.ReadByte()
if err != nil {
return
}
if b2 == 0 {
err = errors.New("got ATypDomain but domain lenth is marked to be 0")
return
}
bs := utils.GetBytes(int(b2))
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != int(b2) {
err = utils.ErrShortRead
return
}
addr.Name = string(bs[:n])
case netLayer.AtypIP4:
bs := make([]byte, 4)
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != 4 {
err = utils.ErrShortRead
return
}
addr.IP = bs
case netLayer.AtypIP6:
bs := make([]byte, net.IPv6len)
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != 4 {
err = utils.ErrShortRead
return
}
addr.IP = bs
default:
err = utils.ErrInvalidData
return
}
return
}
//依照 vless 协议的格式 依次写入 地址的 port, 域名/ip 信息
func WriteAddrTo(writeBuf utils.ByteWriter, raddr netLayer.Addr) {
writeBuf.WriteByte(byte(raddr.Port >> 8))

View File

@@ -1,6 +1,7 @@
package vmess
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
@@ -157,20 +158,17 @@ func (c *Client) commonHandshake(underlay net.Conn, firstPayload []byte, target
// Request
if target.IsUDP() {
err = conn.handshake(CmdUDP)
err = conn.handshake(CmdUDP, firstPayload)
conn.theTarget = target
} else {
err = conn.handshake(CmdTCP)
err = conn.handshake(CmdTCP, firstPayload)
}
if err != nil {
return nil, err
}
if len(firstPayload) > 0 {
_, err = conn.Write(firstPayload)
}
return conn, err
}
@@ -197,6 +195,8 @@ type ClientConn struct {
dataReader io.Reader
dataWriter io.Writer
vmessout []byte
}
func (c *ClientConn) CloseConnWithRaddr(_ netLayer.Addr) error {
@@ -228,7 +228,7 @@ func (c *ClientConn) WriteMsgTo(b []byte, _ netLayer.Addr) error {
}
// handshake sends request to server.
func (c *ClientConn) handshake(cmd byte) error {
func (c *ClientConn) handshake(cmd byte, firstpayload []byte) error {
buf := utils.GetBuf()
defer utils.PutBuf(buf)
@@ -273,8 +273,11 @@ func (c *ClientConn) handshake(cmd byte) error {
var fixedLengthCmdKey [16]byte
copy(fixedLengthCmdKey[:], GetKey(c.V2rayUser))
vmessout := sealVMessAEADHeader(fixedLengthCmdKey, buf.Bytes(), time.Now())
_, err = c.Conn.Write(vmessout)
vmessout := sealAEADHeader(fixedLengthCmdKey, buf.Bytes(), time.Now())
c.vmessout = vmessout
_, err = c.Write(firstpayload)
return err
}
@@ -331,17 +334,37 @@ func (c *ClientConn) Write(b []byte) (n int, err error) {
if c.dataWriter != nil {
return c.dataWriter.Write(b)
}
c.dataWriter = c.Conn
switchChan := make(chan struct{})
var outBuf *bytes.Buffer
if len(b) == 0 {
_, err = c.Conn.Write(c.vmessout)
c.vmessout = nil
if err != nil {
return 0, err
}
} else {
outBuf = bytes.NewBuffer(c.vmessout)
writer := &utils.WriteSwitcher{
Old: outBuf,
New: c.Conn,
SwitchChan: switchChan,
Closer: c.Conn,
}
c.dataWriter = writer
}
if c.opt&OptChunkStream == OptChunkStream {
switch c.security {
case SecurityNone:
c.dataWriter = ChunkedWriter(c.Conn)
c.dataWriter = ChunkedWriter(c.dataWriter)
case SecurityAES128GCM:
block, _ := aes.NewCipher(c.reqBodyKey[:])
aead, _ := cipher.NewGCM(block)
c.dataWriter = AEADWriter(c.Conn, aead, c.reqBodyIV[:])
c.dataWriter = AEADWriter(c.dataWriter, aead, c.reqBodyIV[:])
case SecurityChacha20Poly1305:
key := utils.GetBytes(32)
@@ -350,12 +373,22 @@ func (c *ClientConn) Write(b []byte) (n int, err error) {
t = md5.Sum(key[:16])
copy(key[16:], t[:])
aead, _ := chacha20poly1305.New(key)
c.dataWriter = AEADWriter(c.Conn, aead, c.reqBodyIV[:])
c.dataWriter = AEADWriter(c.dataWriter, aead, c.reqBodyIV[:])
utils.PutBytes(key)
}
}
return c.dataWriter.Write(b)
n, err = c.dataWriter.Write(b)
if len(b) != 0 {
close(switchChan)
c.vmessout = nil
}
if err != nil {
return
}
_, err = c.Conn.Write(outBuf.Bytes())
return
}
func (c *ClientConn) Read(b []byte) (n int, err error) {

View File

@@ -30,6 +30,8 @@ const (
kdfSaltConstVMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length"
)
const authid_len = 16
func kdf(key []byte, path ...string) []byte {
hmacCreator := &hMacCreator{value: []byte(kdfSaltConstVMessAEADKDF)}
for _, v := range path {
@@ -58,7 +60,7 @@ func (h *hMacCreator) Create() hash.Hash {
}
//https://github.com/v2fly/v2fly-github-io/issues/20
func createAuthID(cmdKey []byte, time int64) [16]byte {
func createAuthID(cmdKey []byte, time int64) (result [authid_len]byte) {
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, time)
@@ -69,7 +71,7 @@ func createAuthID(cmdKey []byte, time int64) [16]byte {
binary.Write(buf, binary.BigEndian, zero)
aesBlock, _ := generateCipher(cmdKey)
var result [16]byte
aesBlock.Encrypt(result[:], buf.Bytes())
return result
}
@@ -78,9 +80,7 @@ func generateCipher(cmdKey []byte) (cipher.Block, error) {
return aes.NewCipher(kdf16(cmdKey, kdfSaltConstAuthIDEncryptionKey))
}
func generateCipherByV2rayUser(u utils.V2rayUser) (cipher.Block, error) {
var fixedLengthCmdKey [16]byte
copy(fixedLengthCmdKey[:], GetKey(u))
return generateCipher(fixedLengthCmdKey[:])
return generateCipher(GetKey(u))
}
//为0表示匹配成功
@@ -90,10 +90,10 @@ func tryMatchAuthIDByBlock(block cipher.Block, bs []byte) (failReason int) {
var rand int32
var zero uint32
if len(bs) < utils.UUID_BytesLen {
if len(bs) < authid_len {
return 1
}
data := utils.GetBytes(utils.UUID_BytesLen)
data := utils.GetBytes(authid_len)
block.Decrypt(data, bs)
buf := bytes.NewBuffer(data)
@@ -114,7 +114,7 @@ func tryMatchAuthIDByBlock(block cipher.Block, bs []byte) (failReason int) {
return 0
}
func sealVMessAEADHeader(key [16]byte, data []byte, t time.Time) []byte {
func sealAEADHeader(key [16]byte, data []byte, t time.Time) []byte {
generatedAuthID := createAuthID(key[:], t.Unix())
connectionNonce := make([]byte, 8)
rand.Read(connectionNonce)

View File

@@ -96,7 +96,7 @@ func (s *Server) addUser(u utils.V2rayUser) {
s.authPairList = append(s.authPairList, p)
}
func (s *Server) Handshake(underlay net.Conn) (result net.Conn, msgConn netLayer.MsgConn, targetAddr netLayer.Addr, returnErr error) {
func (s *Server) Handshake(underlay net.Conn) (tcpConn net.Conn, msgConn netLayer.MsgConn, targetAddr netLayer.Addr, returnErr error) {
if err := proxy.SetCommonReadTimeout(underlay); err != nil {
returnErr = err
return
@@ -109,18 +109,18 @@ func (s *Server) Handshake(underlay net.Conn) (result net.Conn, msgConn netLayer
if err != nil {
returnErr = err
return
} else if n < utils.UUID_BytesLen {
} else if n < authid_len {
returnErr = utils.NumErr{E: utils.ErrInvalidData, N: 1}
return
}
user, ok := authUserByAuthPairList(data[:utils.UUID_BytesLen], s.authPairList)
user, ok := authUserByAuthPairList(data[:authid_len], s.authPairList)
if !ok {
returnErr = utils.NumErr{E: utils.ErrInvalidData, N: 2}
return
}
cmdKey := GetKey(user)
remainBuf := bytes.NewBuffer(data[utils.UUID_BytesLen:n])
remainBuf := bytes.NewBuffer(data[authid_len:n])
aeadData, shouldDrain, bytesRead, errorReason := openAEADHeader(cmdKey, data[:16], remainBuf)
if errorReason != nil {
@@ -160,7 +160,7 @@ func (s *Server) Handshake(underlay net.Conn) (result net.Conn, msgConn netLayer
switch sc.cmd {
//我们 不支持vmess 的 mux.cool
case CmdTCP, CmdUDP:
ad, err := GetAddrFrom(aeadDataBuf)
ad, err := netLayer.V2rayGetAddrFrom(aeadDataBuf)
if err != nil {
returnErr = utils.NumErr{E: utils.ErrInvalidData, N: 3}
return
@@ -183,14 +183,19 @@ func (s *Server) Handshake(underlay net.Conn) (result net.Conn, msgConn netLayer
fnv1a.Write(F)
*/
sc.remainBuf = remainBuf
sc.remainReadBuf = remainBuf
buf := utils.GetBuf()
sc.aead_encodeRespHeader(buf)
sc.Conn.Write(buf.Bytes())
sc.firstWriteBuf = buf
result = sc
if sc.cmd == CmdTCP {
tcpConn = sc
} else {
msgConn = sc
}
return
}
@@ -212,7 +217,7 @@ type ServerConn struct {
respBodyIV [16]byte
respBodyKey [16]byte
remainBuf *bytes.Buffer
remainReadBuf, firstWriteBuf *bytes.Buffer
dataReader io.Reader
dataWriter io.Writer
@@ -261,17 +266,26 @@ func (c *ServerConn) Write(b []byte) (n int, err error) {
if c.dataWriter != nil {
return c.dataWriter.Write(b)
}
switchChan := make(chan struct{})
c.dataWriter = c.Conn
//使用 WriteSwitcher 来 粘连 服务器vmess响应 以及第一个数据响应
writer := &utils.WriteSwitcher{
Old: c.firstWriteBuf,
New: c.Conn,
SwitchChan: switchChan,
Closer: c.Conn,
}
c.dataWriter = writer
if c.opt&OptChunkStream == OptChunkStream {
switch c.security {
case SecurityNone:
c.dataWriter = ChunkedWriter(c.Conn)
c.dataWriter = ChunkedWriter(writer)
case SecurityAES128GCM:
block, _ := aes.NewCipher(c.respBodyKey[:])
aead, _ := cipher.NewGCM(block)
c.dataWriter = AEADWriter(c.Conn, aead, c.respBodyIV[:])
c.dataWriter = AEADWriter(writer, aead, c.respBodyIV[:])
case SecurityChacha20Poly1305:
key := utils.GetBytes(32)
@@ -280,12 +294,22 @@ func (c *ServerConn) Write(b []byte) (n int, err error) {
t = md5.Sum(key[:16])
copy(key[16:], t[:])
aead, _ := chacha20poly1305.New(key)
c.dataWriter = AEADWriter(c.Conn, aead, c.respBodyIV[:])
c.dataWriter = AEADWriter(writer, aead, c.respBodyIV[:])
utils.PutBytes(key)
}
}
return c.dataWriter.Write(b)
n, err = c.dataWriter.Write(b)
close(switchChan)
if err != nil {
return
}
_, err = c.Conn.Write(c.firstWriteBuf.Bytes())
utils.PutBuf(c.firstWriteBuf)
c.firstWriteBuf = nil
return
}
func (c *ServerConn) Read(b []byte) (n int, err error) {
@@ -294,8 +318,8 @@ func (c *ServerConn) Read(b []byte) (n int, err error) {
return c.dataReader.Read(b)
}
var curReader io.Reader
if c.remainBuf != nil && c.remainBuf.Len() > 0 {
curReader = io.MultiReader(c.remainBuf, c.Conn)
if c.remainReadBuf != nil && c.remainReadBuf.Len() > 0 {
curReader = io.MultiReader(c.remainReadBuf, c.Conn)
} else {
curReader = c.Conn
@@ -327,3 +351,28 @@ func (c *ServerConn) Read(b []byte) (n int, err error) {
return c.dataReader.Read(b)
}
func (c *ServerConn) ReadMsgFrom() (bs []byte, target netLayer.Addr, err error) {
bs = utils.GetPacket()
var n int
n, err = c.Read(bs)
if err != nil {
utils.PutPacket(bs)
bs = nil
return
}
bs = bs[:n]
target = c.theTarget
return
}
func (c *ServerConn) WriteMsgTo(b []byte, _ netLayer.Addr) error {
_, e := c.Write(b)
return e
}
func (c *ServerConn) CloseConnWithRaddr(_ netLayer.Addr) error {
return c.Conn.Close()
}
func (c *ServerConn) Fullcone() bool {
return false
}

View File

@@ -9,16 +9,25 @@ from github.com/Dreamacro/clash/tree/master/transport/vmess/
aead:
https://github.com/v2fly/v2fly-github-io/issues/20
Implementation Details
本作在chash 的 vmess 客户端的 基础上,反推出了 对称的 vmess 服务端,不过为了方便,也使用了 v2ray的 OpenVMessAEADHeader 函数.
实际上 clash的 sealAEADHeader 的代码也是 和v2ray的响应代码十分接近的。这倒不重要因为文档给出的算法是固定的所以实现代码都是一样的。二者区别主要是读写代码的结构。
vmess 协议是一个很老旧的协议有很多向前兼容的代码很多地方都已经废弃了我们这里只支持最新的aead.
我们所实现的vmess 服务端 力求简单、最新,不求兼容所有老旧客户端。
*/
package vmess
import (
"crypto/md5"
"encoding/binary"
"errors"
"net"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
)
@@ -68,89 +77,3 @@ func TimestampHash(unixSec int64) []byte {
md5hash.Write(ts)
return md5hash.Sum(nil)
}
//依照 vmess 协议的格式 依次读取 地址的 port, 域名/ip 信息(与vless相同)
func GetAddrFrom(buf utils.ByteReader) (addr netLayer.Addr, err error) {
pb1, err := buf.ReadByte()
if err != nil {
return
}
pb2, err := buf.ReadByte()
if err != nil {
return
}
port := uint16(pb1)<<8 + uint16(pb2)
if port == 0 {
err = utils.ErrInvalidData
return
}
addr.Port = int(port)
var b1 byte
b1, err = buf.ReadByte()
if err != nil {
return
}
switch b1 {
case netLayer.AtypDomain:
var b2 byte
b2, err = buf.ReadByte()
if err != nil {
return
}
if b2 == 0 {
err = errors.New("got ATypDomain but domain lenth is marked to be 0")
return
}
bs := utils.GetBytes(int(b2))
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != int(b2) {
err = utils.ErrShortRead
return
}
addr.Name = string(bs[:n])
case netLayer.AtypIP4:
bs := make([]byte, 4)
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != 4 {
err = utils.ErrShortRead
return
}
addr.IP = bs
case netLayer.AtypIP6:
bs := make([]byte, net.IPv6len)
var n int
n, err = buf.Read(bs)
if err != nil {
return
}
if n != 4 {
err = utils.ErrShortRead
return
}
addr.IP = bs
default:
err = utils.ErrInvalidData
return
}
return
}

View File

@@ -5,6 +5,13 @@ import (
"sync"
)
type WriteWrapper interface {
io.Writer
GetRawWriter() io.Writer
SetRawWriter(io.Writer)
}
// bufio.Reader and bytes.Buffer implemented ByteReader
type ByteReader interface {
ReadByte() (byte, error)
@@ -146,17 +153,25 @@ type WriteSwitcher struct {
Old, New io.Writer //non-nil
SwitchChan chan struct{} //non-nil
io.Closer
switched bool
}
func (d *WriteSwitcher) Write(p []byte) (int, error) {
select {
case <-d.SwitchChan:
return d.New.Write(p)
if !d.switched {
select {
case <-d.SwitchChan:
d.switched = true
return d.New.Write(p)
default:
return d.Old.Write(p)
default:
return d.Old.Write(p)
}
} else {
return d.New.Write(p)
}
}
func (d *WriteSwitcher) Close() error {