From 4bec9ab84536024c57f5459a36f3080e279178b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Thu, 4 Jul 2024 19:59:18 +0000 Subject: [PATCH] Add H2 path support for fallback --- proxy/trojan/server.go | 52 +++++++++++++++++++++++++++++---- proxy/vless/inbound/inbound.go | 53 ++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/proxy/trojan/server.go b/proxy/trojan/server.go index 6110eec0..1d136af1 100644 --- a/proxy/trojan/server.go +++ b/proxy/trojan/server.go @@ -1,7 +1,10 @@ package trojan import ( + "bufio" "context" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" "io" "strconv" "strings" @@ -346,14 +349,14 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S cs := tlsConn.ConnectionState() name = cs.ServerName alpn = cs.NegotiatedProtocol - errors.LogInfo(ctx, "realName = " + name) - errors.LogInfo(ctx, "realAlpn = " + alpn) + errors.LogInfo(ctx, "realName = "+name) + errors.LogInfo(ctx, "realAlpn = "+alpn) } else if realityConn, ok := iConn.(*reality.Conn); ok { cs := realityConn.ConnectionState() name = cs.ServerName alpn = cs.NegotiatedProtocol - errors.LogInfo(ctx, "realName = " + name) - errors.LogInfo(ctx, "realAlpn = " + alpn) + errors.LogInfo(ctx, "realName = "+name) + errors.LogInfo(ctx, "realAlpn = "+alpn) } name = strings.ToLower(name) alpn = strings.ToLower(alpn) @@ -403,7 +406,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S } if k == '?' || k == ' ' { path = string(firstBytes[i:j]) - errors.LogInfo(ctx, "realPath = " + path) + errors.LogInfo(ctx, "realPath = "+path) if pfb[path] == nil { path = "" } @@ -413,6 +416,11 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S break } } + } else if firstLen >= 18 && first.Byte(4) == '*' { // process h2c + h2path := extractPathFromH2Request(connection) + if h2path != "" { + path = h2path + } } } fb := pfb[path] @@ -520,3 +528,37 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S return nil } + +// Get path form http2 +func extractPathFromH2Request(conn stat.Connection) string { + reader := bufio.NewReader(conn) + framer := http2.NewFramer(conn, reader) + + for { + frame, err := framer.ReadFrame() + if err != nil { + return "" + } + + // find headers frame + if f, ok := frame.(*http2.HeadersFrame); ok { + decoder := hpack.NewDecoder(4096, func(hf hpack.HeaderField) {}) + headerBlock := f.HeaderBlockFragment() + + hf, err := decoder.DecodeFull(headerBlock) + if err != nil { + return "" + } + path := func(headers []hpack.HeaderField) string { + for _, header := range headers { + if header.Name == ":path" { + return header.Value + } + } + return "" + } + + return path(hf) + } + } +} diff --git a/proxy/vless/inbound/inbound.go b/proxy/vless/inbound/inbound.go index 891f725d..e8372463 100644 --- a/proxy/vless/inbound/inbound.go +++ b/proxy/vless/inbound/inbound.go @@ -3,6 +3,7 @@ package inbound //go:generate go run github.com/xtls/xray-core/common/errors/errorgen import ( + "bufio" "bytes" "context" gotls "crypto/tls" @@ -13,6 +14,9 @@ import ( "time" "unsafe" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" @@ -223,14 +227,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s cs := tlsConn.ConnectionState() name = cs.ServerName alpn = cs.NegotiatedProtocol - errors.LogInfo(ctx, "realName = " + name) - errors.LogInfo(ctx, "realAlpn = " + alpn) + errors.LogInfo(ctx, "realName = "+name) + errors.LogInfo(ctx, "realAlpn = "+alpn) } else if realityConn, ok := iConn.(*reality.Conn); ok { cs := realityConn.ConnectionState() name = cs.ServerName alpn = cs.NegotiatedProtocol - errors.LogInfo(ctx, "realName = " + name) - errors.LogInfo(ctx, "realAlpn = " + alpn) + errors.LogInfo(ctx, "realName = "+name) + errors.LogInfo(ctx, "realAlpn = "+alpn) } name = strings.ToLower(name) alpn = strings.ToLower(alpn) @@ -295,7 +299,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s } if k == '?' || k == ' ' { path = string(firstBytes[i:j]) - errors.LogInfo(ctx, "realPath = " + path) + errors.LogInfo(ctx, "realPath = "+path) if pfb[path] == nil { path = "" } @@ -305,6 +309,11 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s break } } + } else if firstLen >= 18 && first.Byte(4) == '*' { // process h2c + h2path := extractPathFromH2Request(connection) + if h2path != "" { + path = h2path + } } } fb := pfb[path] @@ -583,3 +592,37 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s return nil } + +// Get path form http2 +func extractPathFromH2Request(conn stat.Connection) string { + reader := bufio.NewReader(conn) + framer := http2.NewFramer(conn, reader) + + for { + frame, err := framer.ReadFrame() + if err != nil { + return "" + } + + // find headers frame + if f, ok := frame.(*http2.HeadersFrame); ok { + decoder := hpack.NewDecoder(4096, func(hf hpack.HeaderField) {}) + headerBlock := f.HeaderBlockFragment() + + hf, err := decoder.DecodeFull(headerBlock) + if err != nil { + return "" + } + path := func(headers []hpack.HeaderField) string { + for _, header := range headers { + if header.Name == ":path" { + return header.Value + } + } + return "" + } + + return path(hf) + } + } +}