From d29370a6544177ff67ff0c7b32af27a0a48da44a Mon Sep 17 00:00:00 2001 From: Darhwa Date: Mon, 18 May 2020 18:27:49 +0800 Subject: [PATCH 1/6] Enhance http outbound 1. Enables http outbound to set up a HTTP tunnel above HTTP/1.1, HTTP/1.1 over TLS, or HTTP/2 over TLS. Previously it only works for plain HTTP/1.1 2. In setting up CONNECT tunnel, replaces handcrafted request with standard http.Request --- proxy/http/client.go | 209 ++++++++++++++++++++++++++++------ transport/internet/tls/tls.go | 14 +-- 2 files changed, 184 insertions(+), 39 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index 51dca746..5dd2d7c0 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -3,10 +3,15 @@ package http import ( + "bufio" "context" "encoding/base64" "io" - "strings" + "net/http" + "net/url" + "sync" + + "golang.org/x/net/http2" "v2ray.com/core" "v2ray.com/core/common" @@ -20,6 +25,7 @@ import ( "v2ray.com/core/features/policy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" + "v2ray.com/core/transport/internet/tls" ) type Client struct { @@ -27,6 +33,16 @@ type Client struct { policyManager policy.Manager } +type h2Conn struct { + rawConn net.Conn + h2Conn *http2.ClientConn +} + +var ( + cachedH2Mutex sync.Mutex + cachedH2Conns map[net.Destination]h2Conn +) + // NewClient create a new http client based on the given config. func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { serverList := protocol.NewServerList() @@ -54,25 +70,26 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } - destination := outbound.Target + target := outbound.Target - if destination.Network == net.Network_UDP { + if target.Network == net.Network_UDP { return newError("UDP is not supported by HTTP outbound") } - var server *protocol.ServerSpec + var user *protocol.MemoryUser var conn internet.Connection if err := retry.ExponentialBackoff(5, 100).On(func() error { - server = c.serverPicker.PickServer() + server := c.serverPicker.PickServer() dest := server.Destination() - rawConn, err := dialer.Dial(ctx, dest) - if err != nil { - return err - } - conn = rawConn + user = server.PickUser() + targetAddr := target.NetAddr() - return nil + netConn, err := setUpHttpTunnel(ctx, dest, targetAddr, user, dialer) + if netConn != nil { + conn = internet.Connection(netConn) + } + return err }); err != nil { return newError("failed to find an available destination").Base(err) } @@ -84,16 +101,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter }() p := c.policyManager.ForLevel(0) - - user := server.PickUser() if user != nil { p = c.policyManager.ForLevel(user.Level) } - if err := setUpHttpTunnel(conn, conn, &destination, user); err != nil { - return err - } - ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) @@ -115,30 +126,164 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } // setUpHttpTunnel will create a socket tunnel via HTTP CONNECT method -func setUpHttpTunnel(reader io.Reader, writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error { - var headers []string - destNetAddr := destination.NetAddr() - headers = append(headers, "CONNECT "+destNetAddr+" HTTP/1.1") - headers = append(headers, "Host: "+destNetAddr) +func setUpHttpTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer) (net.Conn, error) { + req := (&http.Request{ + Method: "CONNECT", + URL: &url.URL{Host: target}, + Header: make(http.Header), + Host: target, + }).WithContext(ctx) + if user != nil && user.Account != nil { account := user.Account.(*Account) auth := account.GetUsername() + ":" + account.GetPassword() - headers = append(headers, "Proxy-Authorization: Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) + req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) } - headers = append(headers, "Proxy-Connection: Keep-Alive") + req.Header.Set("Proxy-Connection", "Keep-Alive") - b := buf.New() - b.WriteString(strings.Join(headers, "\r\n") + "\r\n\r\n") - if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil { - return err + connectHttp1 := func(rawConn net.Conn) (net.Conn, error) { + req.Proto = "HTTP/1.1" + req.ProtoMajor = 1 + req.ProtoMinor = 1 + + err := req.Write(rawConn) + if err != nil { + rawConn.Close() + return nil, err + } + + resp, err := http.ReadResponse(bufio.NewReader(rawConn), req) + if err != nil { + rawConn.Close() + return nil, err + } + + if resp.StatusCode != http.StatusOK { + rawConn.Close() + return nil, newError("Proxy responded with non 200 code: " + resp.Status) + } + return rawConn, nil } - b.Clear() - if _, err := b.ReadFrom(reader); err != nil { - return err + connectHttp2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) { + req.Proto = "HTTP/2.0" + req.ProtoMajor = 2 + req.ProtoMinor = 0 + pr, pw := io.Pipe() + req.Body = pr + + resp, err := h2clientConn.RoundTrip(req) + if err != nil { + rawConn.Close() + return nil, err + } + + if resp.StatusCode != http.StatusOK { + rawConn.Close() + return nil, newError("Proxy responded with non 200 code: " + resp.Status) + } + return newHttp2Conn(rawConn, pw, resp.Body), nil } - return nil + cachedH2Mutex.Lock() + defer cachedH2Mutex.Unlock() + + if cachedConn, found := cachedH2Conns[dest]; found { + if cachedConn.rawConn != nil && cachedConn.h2Conn != nil { + rc := cachedConn.rawConn + cc := cachedConn.h2Conn + if cc.CanTakeNewRequest() { + proxyConn, err := connectHttp2(rc, cc) + if err != nil { + return nil, err + } + + return proxyConn, nil + } + } + } + + rawConn, err := dialer.Dial(ctx, dest) + if err != nil { + return nil, err + } + + nextProto := "" + if tlsConn, ok := rawConn.(*tls.Conn); ok { + if err := tlsConn.Handshake(); err != nil { + rawConn.Close() + return nil, err + } + nextProto = tlsConn.ConnectionState().NegotiatedProtocol + } + + switch nextProto { + case "": + fallthrough + case "http/1.1": + return connectHttp1(rawConn) + case "h2": + t := http2.Transport{} + h2clientConn, err := t.NewClientConn(rawConn) + if err != nil { + rawConn.Close() + return nil, err + } + + proxyConn, err := connectHttp2(rawConn, h2clientConn) + if err != nil { + rawConn.Close() + return nil, err + } + + if cachedH2Conns == nil { + cachedH2Conns = make(map[net.Destination]h2Conn) + } + + cachedH2Conns[dest] = h2Conn{ + rawConn: rawConn, + h2Conn: h2clientConn, + } + + return proxyConn, err + default: + return nil, newError("negotiated unsupported application layer protocol: " + nextProto) + } +} + +func newHttp2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn { + return &http2Conn{Conn: c, in: pipedReqBody, out: respBody} +} + +type http2Conn struct { + net.Conn + in *io.PipeWriter + out io.ReadCloser +} + +func (h *http2Conn) Read(p []byte) (n int, err error) { + return h.out.Read(p) +} + +func (h *http2Conn) Write(p []byte) (n int, err error) { + return h.in.Write(p) +} + +func (h *http2Conn) Close() error { + h.in.Close() + return h.out.Close() +} + +func (h *http2Conn) CloseConn() error { + return h.Conn.Close() +} + +func (h *http2Conn) CloseWrite() error { + return h.in.Close() +} + +func (h *http2Conn) CloseRead() error { + return h.out.Close() } func init() { diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index 90b60e02..baf5d393 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -14,25 +14,25 @@ import ( //go:generate errorgen var ( - _ buf.Writer = (*conn)(nil) + _ buf.Writer = (*Conn)(nil) ) -type conn struct { +type Conn struct { *tls.Conn } -func (c *conn) WriteMultiBuffer(mb buf.MultiBuffer) error { +func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error { mb = buf.Compact(mb) mb, err := buf.WriteMultiBuffer(c, mb) buf.ReleaseMulti(mb) return err } -func (c *conn) HandshakeAddress() net.Address { +func (c *Conn) HandshakeAddress() net.Address { if err := c.Handshake(); err != nil { return nil } - state := c.Conn.ConnectionState() + state := c.ConnectionState() if state.ServerName == "" { return nil } @@ -42,7 +42,7 @@ func (c *conn) HandshakeAddress() net.Address { // Client initiates a TLS client handshake on the given connection. func Client(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Client(c, config) - return &conn{Conn: tlsConn} + return &Conn{Conn: tlsConn} } func copyConfig(c *tls.Config) *utls.Config { @@ -63,5 +63,5 @@ func UClient(c net.Conn, config *tls.Config) net.Conn { // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) - return &conn{Conn: tlsConn} + return &Conn{Conn: tlsConn} } From 90af5f19ba3e9dbbab72368eb5704370843beadd Mon Sep 17 00:00:00 2001 From: Roger Shimizu Date: Wed, 24 Jun 2020 12:57:03 +0800 Subject: [PATCH 2/6] Apply Debian's patch to upstream --- app/dns/dohdns.go | 2 +- app/dns/udpns.go | 2 +- app/proxyman/command/command.go | 0 common/crypto/chunk.go | 0 common/protocol/account.go | 0 common/protocol/context.go | 0 common/protocol/id.go | 0 common/signal/done/done.go | 0 common/signal/notifier.go | 0 common/uuid/uuid.go | 0 config.go | 0 core.go | 0 proxy/http/server.go | 0 proxy/proxy.go | 0 proxy/vmess/account.pb.go | 0 proxy/vmess/account.proto | 0 transport/internet/kcp/segment.go | 0 transport/internet/system_listener.go | 16 ++++++++-------- transport/internet/websocket/ws.go | 0 v2ray.go | 0 20 files changed, 10 insertions(+), 10 deletions(-) mode change 100755 => 100644 app/proxyman/command/command.go mode change 100755 => 100644 common/crypto/chunk.go mode change 100755 => 100644 common/protocol/account.go mode change 100755 => 100644 common/protocol/context.go mode change 100755 => 100644 common/protocol/id.go mode change 100755 => 100644 common/signal/done/done.go mode change 100755 => 100644 common/signal/notifier.go mode change 100755 => 100644 common/uuid/uuid.go mode change 100755 => 100644 config.go mode change 100755 => 100644 core.go mode change 100755 => 100644 proxy/http/server.go mode change 100755 => 100644 proxy/proxy.go mode change 100755 => 100644 proxy/vmess/account.pb.go mode change 100755 => 100644 proxy/vmess/account.proto mode change 100755 => 100644 transport/internet/kcp/segment.go mode change 100755 => 100644 transport/internet/websocket/ws.go mode change 100755 => 100644 v2ray.go diff --git a/app/dns/dohdns.go b/app/dns/dohdns.go index 89f96df8..26af4937 100644 --- a/app/dns/dohdns.go +++ b/app/dns/dohdns.go @@ -249,7 +249,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPO b, _ := dns.PackMessage(r.msg) resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes()) if err != nil { - newError("failed to retrive response").Base(err).AtError().WriteToLog() + newError("failed to retrieve response").Base(err).AtError().WriteToLog() return } rec, err := parseResponse(resp) diff --git a/app/dns/udpns.go b/app/dns/udpns.go index 70148fde..f1f60aca 100644 --- a/app/dns/udpns.go +++ b/app/dns/udpns.go @@ -108,7 +108,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot ipRec, err := parseResponse(packet.Payload.Bytes()) if err != nil { - newError(s.name, " fail to parse responsed DNS udp").AtError().WriteToLog() + newError(s.name, " fail to parse responded DNS udp").AtError().WriteToLog() return } diff --git a/app/proxyman/command/command.go b/app/proxyman/command/command.go old mode 100755 new mode 100644 diff --git a/common/crypto/chunk.go b/common/crypto/chunk.go old mode 100755 new mode 100644 diff --git a/common/protocol/account.go b/common/protocol/account.go old mode 100755 new mode 100644 diff --git a/common/protocol/context.go b/common/protocol/context.go old mode 100755 new mode 100644 diff --git a/common/protocol/id.go b/common/protocol/id.go old mode 100755 new mode 100644 diff --git a/common/signal/done/done.go b/common/signal/done/done.go old mode 100755 new mode 100644 diff --git a/common/signal/notifier.go b/common/signal/notifier.go old mode 100755 new mode 100644 diff --git a/common/uuid/uuid.go b/common/uuid/uuid.go old mode 100755 new mode 100644 diff --git a/config.go b/config.go old mode 100755 new mode 100644 diff --git a/core.go b/core.go old mode 100755 new mode 100644 diff --git a/proxy/http/server.go b/proxy/http/server.go old mode 100755 new mode 100644 diff --git a/proxy/proxy.go b/proxy/proxy.go old mode 100755 new mode 100644 diff --git a/proxy/vmess/account.pb.go b/proxy/vmess/account.pb.go old mode 100755 new mode 100644 diff --git a/proxy/vmess/account.proto b/proxy/vmess/account.proto old mode 100755 new mode 100644 diff --git a/transport/internet/kcp/segment.go b/transport/internet/kcp/segment.go old mode 100755 new mode 100644 diff --git a/transport/internet/system_listener.go b/transport/internet/system_listener.go index 3ab8d3a8..4c85c9ae 100644 --- a/transport/internet/system_listener.go +++ b/transport/internet/system_listener.go @@ -15,10 +15,10 @@ var ( type controller func(network, address string, fd uintptr) error type DefaultListener struct { - contollers []controller + controllers []controller } -func getControlFunc(ctx context.Context, sockopt *SocketConfig, contollers []controller) func(network, address string, c syscall.RawConn) error { +func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []controller) func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { if sockopt != nil { @@ -27,7 +27,7 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, contollers []con } } - for _, controller := range contollers { + for _, controller := range controllers { if err := controller(network, address, fd); err != nil { newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) } @@ -39,8 +39,8 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, contollers []con func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) { var lc net.ListenConfig - if sockopt != nil || len(dl.contollers) > 0 { - lc.Control = getControlFunc(ctx, sockopt, dl.contollers) + if sockopt != nil || len(dl.controllers) > 0 { + lc.Control = getControlFunc(ctx, sockopt, dl.controllers) } return lc.Listen(ctx, addr.Network(), addr.String()) @@ -49,8 +49,8 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S func (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) { var lc net.ListenConfig - if sockopt != nil || len(dl.contollers) > 0 { - lc.Control = getControlFunc(ctx, sockopt, dl.contollers) + if sockopt != nil || len(dl.controllers) > 0 { + lc.Control = getControlFunc(ctx, sockopt, dl.controllers) } return lc.ListenPacket(ctx, addr.Network(), addr.String()) @@ -65,6 +65,6 @@ func RegisterListenerController(controller func(network, address string, fd uint return newError("nil listener controller") } - effectiveListener.contollers = append(effectiveListener.contollers, controller) + effectiveListener.controllers = append(effectiveListener.controllers, controller) return nil } diff --git a/transport/internet/websocket/ws.go b/transport/internet/websocket/ws.go old mode 100755 new mode 100644 diff --git a/v2ray.go b/v2ray.go old mode 100755 new mode 100644 From bde766770d4434d67c05231cc8a0d645b0e348ae Mon Sep 17 00:00:00 2001 From: DuckSoft Date: Thu, 25 Jun 2020 14:10:24 +0800 Subject: [PATCH 3/6] dohdns.go: multiple typo fixes --- app/dns/dohdns.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/dns/dohdns.go b/app/dns/dohdns.go index 26af4937..1c183fb6 100644 --- a/app/dns/dohdns.go +++ b/app/dns/dohdns.go @@ -27,9 +27,9 @@ import ( "v2ray.com/core/transport/internet" ) -// DoHNameServer implimented DNS over HTTPS (RFC8484) Wire Format, -// which is compatiable with traditional dns over udp(RFC1035), -// thus most of the DOH implimentation is copied from udpns.go +// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format, +// which is compatible with traditional dns over udp(RFC1035), +// thus most of the DOH implementation is copied from udpns.go type DoHNameServer struct { sync.RWMutex ips map[string]record @@ -48,11 +48,11 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net. newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog() s := baseDOHNameServer(url, "DOH", clientIP) - // Dispatched connection will be closed (interupted) after each request - // This makes DOH inefficient without a keeped-alive connection + // Dispatched connection will be closed (interrupted) after each request + // This makes DOH inefficient without a keep-alived connection // See: core/app/proxyman/outbound/handler.go:113 // Using mux (https request wrapped in a stream layer) improves the situation. - // Recommand to use NewDoHLocalNameServer (DOHL:) if v2ray instance is running on + // Recommend to use NewDoHLocalNameServer (DOHL:) if v2ray instance is running on // a normal network eg. the server side of v2ray tr := &http.Transport{ MaxIdleConns: 30, @@ -191,7 +191,7 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) { updated = true } } - newError(s.name, " got answere: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() + newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() if updated { s.ips[req.domain] = rec From 780318c5c8ea8ac9d990f8423d7710f2500e4a6b Mon Sep 17 00:00:00 2001 From: DuckSoft Date: Thu, 25 Jun 2020 13:52:00 +0800 Subject: [PATCH 4/6] handle dns.PackMessage error this will fix #2599 --- app/dns/dohdns.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/dns/dohdns.go b/app/dns/dohdns.go index 1c183fb6..02b0906c 100644 --- a/app/dns/dohdns.go +++ b/app/dns/dohdns.go @@ -246,7 +246,11 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPO dnsCtx, cancel := context.WithDeadline(dnsCtx, deadline) defer cancel() - b, _ := dns.PackMessage(r.msg) + b, err := dns.PackMessage(r.msg) + if err != nil { + newError("failed to pack dns query").Base(err).AtError().WriteToLog() + return + } resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes()) if err != nil { newError("failed to retrieve response").Base(err).AtError().WriteToLog() From f19f95af3546decd86ec825473da3f127ad683e8 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Fri, 26 Jun 2020 20:15:37 +0800 Subject: [PATCH 5/6] Update AEAD design to rely more on AEAD --- proxy/vmess/aead/consts.go | 8 +- proxy/vmess/aead/encrypt.go | 162 ++++++++++++++++++++++-------------- 2 files changed, 102 insertions(+), 68 deletions(-) diff --git a/proxy/vmess/aead/consts.go b/proxy/vmess/aead/consts.go index 838d1835..7a62fc53 100644 --- a/proxy/vmess/aead/consts.go +++ b/proxy/vmess/aead/consts.go @@ -12,10 +12,10 @@ const KDFSaltConst_AEADRespHeaderPayloadIV = "AEAD Resp Header IV" const KDFSaltConst_VMessAEADKDF = "VMess AEAD KDF" -const KDFSaltConst_VmessAuthIDCheckValue = "VMess AuthID Check Value" - -const KDFSaltConst_VMessLengthMask = "VMess AuthID Mask Value" - const KDFSaltConst_VMessHeaderPayloadAEADKey = "VMess Header AEAD Key" const KDFSaltConst_VMessHeaderPayloadAEADIV = "VMess Header AEAD Nonce" + +const KDFSaltConst_VMessHeaderPayloadLengthAEADKey = "VMess Header AEAD Key_Length" + +const KDFSaltConst_VMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length" diff --git a/proxy/vmess/aead/encrypt.go b/proxy/vmess/aead/encrypt.go index 74e5c954..21bd9467 100644 --- a/proxy/vmess/aead/encrypt.go +++ b/proxy/vmess/aead/encrypt.go @@ -4,10 +4,8 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/hmac" "crypto/rand" "encoding/binary" - "errors" "io" "time" "v2ray.com/core/common" @@ -28,39 +26,54 @@ func SealVMessAEADHeader(key [16]byte, data []byte) []byte { common.Must(binary.Write(aeadPayloadLengthSerializeBuffer, binary.BigEndian, headerPayloadDataLen)) - authidCheckValue := KDF16(key[:], KDFSaltConst_VmessAuthIDCheckValue, string(generatedAuthID[:]), string(aeadPayloadLengthSerializeBuffer.Bytes()), string(connectionNonce)) - aeadPayloadLengthSerializedByte := aeadPayloadLengthSerializeBuffer.Bytes() + var payloadHeaderLengthAEADEncrypted []byte - aeadPayloadLengthMask := KDF16(key[:], KDFSaltConst_VMessLengthMask, string(generatedAuthID[:]), string(connectionNonce[:]))[:2] + { + payloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADKey, string(generatedAuthID[:]), string(connectionNonce)) - aeadPayloadLengthSerializedByte[0] = aeadPayloadLengthSerializedByte[0] ^ aeadPayloadLengthMask[0] - aeadPayloadLengthSerializedByte[1] = aeadPayloadLengthSerializedByte[1] ^ aeadPayloadLengthMask[1] + payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12] - payloadHeaderAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce)) + payloadHeaderLengthAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey) + if err != nil { + panic(err.Error()) + } - payloadHeaderAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12] + payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderLengthAEADAESBlock) - payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey) - if err != nil { - panic(err.Error()) + if err != nil { + panic(err.Error()) + } + + payloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:]) } - payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) + var payloadHeaderAEADEncrypted []byte - if err != nil { - panic(err.Error()) + { + payloadHeaderAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce)) + + payloadHeaderAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12] + + payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey) + if err != nil { + panic(err.Error()) + } + + payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) + + if err != nil { + panic(err.Error()) + } + + payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:]) } - payloadHeaderAEADEncrypted := payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:]) - var outputBuffer = bytes.NewBuffer(nil) common.Must2(outputBuffer.Write(generatedAuthID[:])) //16 - common.Must2(outputBuffer.Write(authidCheckValue)) //16 - - common.Must2(outputBuffer.Write(aeadPayloadLengthSerializedByte)) //2 + common.Must2(outputBuffer.Write(payloadHeaderLengthAEADEncrypted)) //2+16 common.Must2(outputBuffer.Write(connectionNonce)) //8 @@ -70,72 +83,93 @@ func SealVMessAEADHeader(key [16]byte, data []byte) []byte { } func OpenVMessAEADHeader(key [16]byte, authid [16]byte, data io.Reader) ([]byte, bool, error, int) { - var authidCheckValue [16]byte - var headerPayloadDataLen [2]byte + var payloadHeaderLengthAEADEncrypted [18]byte var nonce [8]byte - authidCheckValueReadBytesCounts, err := io.ReadFull(data, authidCheckValue[:]) - if err != nil { - return nil, false, err, authidCheckValueReadBytesCounts - } + var bytesRead int - headerPayloadDataLenReadBytesCounts, err := io.ReadFull(data, headerPayloadDataLen[:]) + authidCheckValueReadBytesCounts, err := io.ReadFull(data, payloadHeaderLengthAEADEncrypted[:]) + bytesRead += authidCheckValueReadBytesCounts if err != nil { - return nil, false, err, authidCheckValueReadBytesCounts + headerPayloadDataLenReadBytesCounts + return nil, false, err, bytesRead } nonceReadBytesCounts, err := io.ReadFull(data, nonce[:]) + bytesRead += nonceReadBytesCounts if err != nil { - return nil, false, err, authidCheckValueReadBytesCounts + headerPayloadDataLenReadBytesCounts + nonceReadBytesCounts + return nil, false, err, bytesRead } - //Unmask Length + //Decrypt Length - LengthMask := KDF16(key[:], KDFSaltConst_VMessLengthMask, string(authid[:]), string(nonce[:]))[:2] + var decryptedAEADHeaderLengthPayloadResult []byte - headerPayloadDataLen[0] = headerPayloadDataLen[0] ^ LengthMask[0] - headerPayloadDataLen[1] = headerPayloadDataLen[1] ^ LengthMask[1] + { + payloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADKey, string(authid[:]), string(nonce[:])) - authidCheckValueReceivedFromNetwork := KDF16(key[:], KDFSaltConst_VmessAuthIDCheckValue, string(authid[:]), string(headerPayloadDataLen[:]), string(nonce[:])) + payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADIV, string(authid[:]), string(nonce[:]))[:12] - if !hmac.Equal(authidCheckValueReceivedFromNetwork, authidCheckValue[:]) { - return nil, true, errCheckMismatch, authidCheckValueReadBytesCounts + headerPayloadDataLenReadBytesCounts + nonceReadBytesCounts + payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey) + if err != nil { + panic(err.Error()) + } + + payloadHeaderLengthAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) + + if err != nil { + panic(err.Error()) + } + + decryptedAEADHeaderLengthPayload, erropenAEAD := payloadHeaderLengthAEAD.Open(nil, payloadHeaderLengthAEADNonce, payloadHeaderLengthAEADEncrypted[:], authid[:]) + + if erropenAEAD != nil { + return nil, true, erropenAEAD, bytesRead + } + + decryptedAEADHeaderLengthPayloadResult = decryptedAEADHeaderLengthPayload } var length uint16 - common.Must(binary.Read(bytes.NewReader(headerPayloadDataLen[:]), binary.BigEndian, &length)) + common.Must(binary.Read(bytes.NewReader(decryptedAEADHeaderLengthPayloadResult[:]), binary.BigEndian, &length)) - payloadHeaderAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadAEADKey, string(authid[:]), string(nonce[:])) + var decryptedAEADHeaderPayloadR []byte - payloadHeaderAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadAEADIV, string(authid[:]), string(nonce[:]))[:12] + var payloadHeaderAEADEncryptedReadedBytesCounts int - //16 == AEAD Tag size - payloadHeaderAEADEncrypted := make([]byte, length+16) + { + payloadHeaderAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadAEADKey, string(authid[:]), string(nonce[:])) - payloadHeaderAEADEncryptedReadedBytesCounts, err := io.ReadFull(data, payloadHeaderAEADEncrypted) - if err != nil { - return nil, false, err, authidCheckValueReadBytesCounts + headerPayloadDataLenReadBytesCounts + payloadHeaderAEADEncryptedReadedBytesCounts + nonceReadBytesCounts + payloadHeaderAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadAEADIV, string(authid[:]), string(nonce[:]))[:12] + + //16 == AEAD Tag size + payloadHeaderAEADEncrypted := make([]byte, length+16) + + payloadHeaderAEADEncryptedReadedBytesCounts, err = io.ReadFull(data, payloadHeaderAEADEncrypted) + bytesRead += payloadHeaderAEADEncryptedReadedBytesCounts + if err != nil { + return nil, false, err, bytesRead + } + + payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey) + if err != nil { + panic(err.Error()) + } + + payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) + + if err != nil { + panic(err.Error()) + } + + decryptedAEADHeaderPayload, erropenAEAD := payloadHeaderAEAD.Open(nil, payloadHeaderAEADNonce, payloadHeaderAEADEncrypted, authid[:]) + + if erropenAEAD != nil { + return nil, true, erropenAEAD, bytesRead + } + + decryptedAEADHeaderPayloadR = decryptedAEADHeaderPayload } - payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey) - if err != nil { - panic(err.Error()) - } - - payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) - - if err != nil { - panic(err.Error()) - } - - decryptedAEADHeaderPayload, erropenAEAD := payloadHeaderAEAD.Open(nil, payloadHeaderAEADNonce, payloadHeaderAEADEncrypted, authid[:]) - - if erropenAEAD != nil { - return nil, true, erropenAEAD, authidCheckValueReadBytesCounts + headerPayloadDataLenReadBytesCounts + payloadHeaderAEADEncryptedReadedBytesCounts + nonceReadBytesCounts - } - - return decryptedAEADHeaderPayload, false, nil, authidCheckValueReadBytesCounts + headerPayloadDataLenReadBytesCounts + payloadHeaderAEADEncryptedReadedBytesCounts + nonceReadBytesCounts + return decryptedAEADHeaderPayloadR, false, nil, bytesRead } - -var errCheckMismatch = errors.New("check verify failed") From 1432278c2ccee4fab3ff33c3f02d15aa549cfdd5 Mon Sep 17 00:00:00 2001 From: rprx <63339210+rprx@users.noreply.github.com> Date: Fri, 26 Jun 2020 21:27:23 +0800 Subject: [PATCH 6/6] Fix typo (#36) --- proxy/vmess/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/vmess/account.go b/proxy/vmess/account.go index 8e621f8d..0f77a51e 100644 --- a/proxy/vmess/account.go +++ b/proxy/vmess/account.go @@ -8,7 +8,7 @@ import ( "v2ray.com/core/common/uuid" ) -// MemoryAccount is an in-memory from of VMess account. +// MemoryAccount is an in-memory form of VMess account. type MemoryAccount struct { // ID is the main ID of the account. ID *protocol.ID