From 8ea496dbfb0f9b6663e27ffbca8c109a9f8f020b Mon Sep 17 00:00:00 2001 From: e1732a364fed <75717694+e1732a364fed@users.noreply.github.com> Date: Sat, 1 Jan 2000 00:00:00 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E8=AE=A2=E4=BB=A3=E7=A0=81;=E7=B2=98?= =?UTF-8?q?=E8=BF=9Evmess=E7=9A=84=E8=AF=B7=E6=B1=82=E5=8C=85=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E9=A6=96=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发现clash的代码似乎没有粘包发送,而是会分包发送,则会造成明显流量特征。 我们粘着发送,就没有 握手包的特征了. 服务端的响应包也同理处理。 --- netLayer/addr.go | 86 +++++++++++++++++++++++++++++++++++ proxy/vless/server.go | 4 +- proxy/vless/udpConn.go | 4 +- proxy/vless/vless.go | 88 ----------------------------------- proxy/vmess/client.go | 59 ++++++++++++++++++------ proxy/vmess/header.go | 16 +++---- proxy/vmess/server.go | 81 ++++++++++++++++++++++++++------- proxy/vmess/vmess.go | 101 +++++------------------------------------ utils/io.go | 25 ++++++++-- 9 files changed, 241 insertions(+), 223 deletions(-) diff --git a/netLayer/addr.go b/netLayer/addr.go index 0f8f918..16fd400 100644 --- a/netLayer/addr.go +++ b/netLayer/addr.go @@ -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 +} diff --git a/proxy/vless/server.go b/proxy/vless/server.go index 524ba1b..394cf3c 100644 --- a/proxy/vless/server.go +++ b/proxy/vless/server.go @@ -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} diff --git a/proxy/vless/udpConn.go b/proxy/vless/udpConn.go index 8109dc9..c9a3c4e 100644 --- a/proxy/vless/udpConn.go +++ b/proxy/vless/udpConn.go @@ -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 } diff --git a/proxy/vless/vless.go b/proxy/vless/vless.go index 67b22b7..35f02bc 100644 --- a/proxy/vless/vless.go +++ b/proxy/vless/vless.go @@ -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)) diff --git a/proxy/vmess/client.go b/proxy/vmess/client.go index 9f6dceb..b81f61e 100644 --- a/proxy/vmess/client.go +++ b/proxy/vmess/client.go @@ -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) { diff --git a/proxy/vmess/header.go b/proxy/vmess/header.go index 037f567..5c19d8b 100644 --- a/proxy/vmess/header.go +++ b/proxy/vmess/header.go @@ -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) diff --git a/proxy/vmess/server.go b/proxy/vmess/server.go index 7b465ab..016f128 100644 --- a/proxy/vmess/server.go +++ b/proxy/vmess/server.go @@ -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 +} diff --git a/proxy/vmess/vmess.go b/proxy/vmess/vmess.go index e9640d8..e4549a8 100644 --- a/proxy/vmess/vmess.go +++ b/proxy/vmess/vmess.go @@ -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 -} diff --git a/utils/io.go b/utils/io.go index 891d61d..0951af7 100644 --- a/utils/io.go +++ b/utils/io.go @@ -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 {