From 271532fc84778fcd056804da2d9e052c0a1bd65a Mon Sep 17 00:00:00 2001 From: RPRX <63339210+rprx@users.noreply.github.com> Date: Sat, 3 Oct 2020 13:12:35 +0000 Subject: [PATCH] Add full VLESS fallbacks support to Trojan (#254) * Add full VLESS fallbacks support to Trojan * Adjustments according to linter * Use common.Must2() for pro.Write() --- infra/conf/trojan.go | 72 +++++++++++---- proxy/trojan/config.pb.go | 86 ++++++++++++------ proxy/trojan/config.proto | 9 +- proxy/trojan/server.go | 187 +++++++++++++++++++++++++++++++++----- 4 files changed, 278 insertions(+), 76 deletions(-) diff --git a/infra/conf/trojan.go b/infra/conf/trojan.go index 4d0c15d89..a633ef76c 100644 --- a/infra/conf/trojan.go +++ b/infra/conf/trojan.go @@ -1,9 +1,11 @@ package conf import ( + "encoding/json" "strconv" "github.com/golang/protobuf/proto" // nolint: staticcheck + "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" @@ -68,8 +70,11 @@ func (c *TrojanClientConfig) Build() (proto.Message, error) { // TrojanInboundFallback is fallback configuration type TrojanInboundFallback struct { - Type string `json:"type"` - Dest string `json:"dest"` + Alpn string `json:"alpn"` + Path string `json:"path"` + Type string `json:"type"` + Dest json.RawMessage `json:"dest"` + Xver uint64 `json:"xver"` } // TrojanUserConfig is user configuration @@ -81,8 +86,9 @@ type TrojanUserConfig struct { // TrojanServerConfig is Inbound configuration type TrojanServerConfig struct { - Clients []*TrojanUserConfig `json:"clients"` - Fallback *TrojanInboundFallback `json:"fallback"` + Clients []*TrojanUserConfig `json:"clients"` + Fallback json.RawMessage `json:"fallback"` + Fallbacks []*TrojanInboundFallback `json:"fallbacks"` } // Build implements Buildable @@ -107,28 +113,56 @@ func (c *TrojanServerConfig) Build() (proto.Message, error) { } if c.Fallback != nil { - fb := &trojan.Fallback{ - Dest: c.Fallback.Dest, + return nil, newError(`Trojan settings: please use "fallbacks":[{}] instead of "fallback":{}`) + } + for _, fb := range c.Fallbacks { + var i uint16 + var s string + if err := json.Unmarshal(fb.Dest, &i); err == nil { + s = strconv.Itoa(int(i)) + } else { + _ = json.Unmarshal(fb.Dest, &s) + } + config.Fallbacks = append(config.Fallbacks, &trojan.Fallback{ + Alpn: fb.Alpn, + Path: fb.Path, + Type: fb.Type, + Dest: s, + Xver: fb.Xver, + }) + } + for _, fb := range config.Fallbacks { + /* + if fb.Alpn == "h2" && fb.Path != "" { + return nil, newError(`Trojan fallbacks: "alpn":"h2" doesn't support "path"`) + } + */ + if fb.Path != "" && fb.Path[0] != '/' { + return nil, newError(`Trojan fallbacks: "path" must be empty or start with "/"`) } - if fb.Type == "" && fb.Dest != "" { - switch fb.Dest[0] { - case '@', '/': - fb.Type = "unix" - default: - if _, err := strconv.Atoi(fb.Dest); err == nil { - fb.Dest = "127.0.0.1:" + fb.Dest - } - if _, _, err := net.SplitHostPort(fb.Dest); err == nil { - fb.Type = "tcp" + if fb.Dest == "serve-ws-none" { + fb.Type = "serve" + } else { + switch fb.Dest[0] { + case '@', '/': + fb.Type = "unix" + default: + if _, err := strconv.Atoi(fb.Dest); err == nil { + fb.Dest = "127.0.0.1:" + fb.Dest + } + if _, _, err := net.SplitHostPort(fb.Dest); err == nil { + fb.Type = "tcp" + } } } } if fb.Type == "" { - return nil, newError("please fill in a valid value for trojan fallback type") + return nil, newError(`Trojan fallbacks: please fill in a valid value for every "dest"`) + } + if fb.Xver > 2 { + return nil, newError(`Trojan fallbacks: invalid PROXY protocol version, "xver" only accepts 0, 1, 2`) } - - config.Fallback = fb } return config, nil diff --git a/proxy/trojan/config.pb.go b/proxy/trojan/config.pb.go index 2d012ff94..79e7bb4de 100644 --- a/proxy/trojan/config.pb.go +++ b/proxy/trojan/config.pb.go @@ -78,8 +78,11 @@ type Fallback struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"` + Alpn string `protobuf:"bytes,1,opt,name=alpn,proto3" json:"alpn,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Dest string `protobuf:"bytes,4,opt,name=dest,proto3" json:"dest,omitempty"` + Xver uint64 `protobuf:"varint,5,opt,name=xver,proto3" json:"xver,omitempty"` } func (x *Fallback) Reset() { @@ -114,6 +117,20 @@ func (*Fallback) Descriptor() ([]byte, []int) { return file_proxy_trojan_config_proto_rawDescGZIP(), []int{1} } +func (x *Fallback) GetAlpn() string { + if x != nil { + return x.Alpn + } + return "" +} + +func (x *Fallback) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + func (x *Fallback) GetType() string { if x != nil { return x.Type @@ -128,6 +145,13 @@ func (x *Fallback) GetDest() string { return "" } +func (x *Fallback) GetXver() uint64 { + if x != nil { + return x.Xver + } + return 0 +} + type ClientConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -180,8 +204,8 @@ type ServerConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` - Fallback *Fallback `protobuf:"bytes,2,opt,name=fallback,proto3" json:"fallback,omitempty"` + Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` + Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` } func (x *ServerConfig) Reset() { @@ -223,9 +247,9 @@ func (x *ServerConfig) GetUsers() []*protocol.User { return nil } -func (x *ServerConfig) GetFallback() *Fallback { +func (x *ServerConfig) GetFallbacks() []*Fallback { if x != nil { - return x.Fallback + return x.Fallbacks } return nil } @@ -242,30 +266,34 @@ var file_proxy_trojan_config_proto_rawDesc = []byte{ 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x25, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x32, 0x0a, 0x08, 0x46, 0x61, - 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, - 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x22, 0x52, - 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, - 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x6e, 0x0a, 0x08, 0x46, 0x61, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x52, 0x0a, 0x0c, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, + 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x87, + 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x36, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x22, 0x85, 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, - 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x08, 0x66, - 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x3f, 0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, + 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, + 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x42, 0x56, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, - 0x52, 0x08, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x56, 0x0a, 0x1b, 0x63, 0x6f, - 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, 0x32, 0x72, - 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, 0x61, 0x79, - 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, - 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, + 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, + 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -292,7 +320,7 @@ var file_proxy_trojan_config_proto_goTypes = []interface{}{ var file_proxy_trojan_config_proto_depIdxs = []int32{ 4, // 0: v2ray.core.proxy.trojan.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint 5, // 1: v2ray.core.proxy.trojan.ServerConfig.users:type_name -> v2ray.core.common.protocol.User - 1, // 2: v2ray.core.proxy.trojan.ServerConfig.fallback:type_name -> v2ray.core.proxy.trojan.Fallback + 1, // 2: v2ray.core.proxy.trojan.ServerConfig.fallbacks:type_name -> v2ray.core.proxy.trojan.Fallback 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name diff --git a/proxy/trojan/config.proto b/proxy/trojan/config.proto index 74092b1eb..cfac0b0e8 100644 --- a/proxy/trojan/config.proto +++ b/proxy/trojan/config.proto @@ -14,8 +14,11 @@ message Account { } message Fallback { - string type = 1; - string dest = 2; + string alpn = 1; + string path = 2; + string type = 3; + string dest = 4; + uint64 xver = 5; } message ClientConfig { @@ -24,5 +27,5 @@ message ClientConfig { message ServerConfig { repeated v2ray.core.common.protocol.User users = 1; - Fallback fallback = 2; + repeated Fallback fallbacks = 3; } diff --git a/proxy/trojan/server.go b/proxy/trojan/server.go index 577828c3a..e9c850e8c 100644 --- a/proxy/trojan/server.go +++ b/proxy/trojan/server.go @@ -4,7 +4,9 @@ package trojan import ( "context" + "crypto/tls" "io" + "strconv" "time" "v2ray.com/core" @@ -23,6 +25,7 @@ import ( "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/udp" + "v2ray.com/core/transport/internet/xtls" ) func init() { @@ -33,9 +36,9 @@ func init() { // Server is an inbound connection handler that handles messages in trojan protocol. type Server struct { - validator *Validator policyManager policy.Manager - config *ServerConfig + validator *Validator + fallbacks map[string]map[string]*Fallback // or nil } // NewServer creates a new trojan inbound handler. @@ -56,7 +59,27 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { server := &Server{ policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), validator: validator, - config: config, + } + + if config.Fallbacks != nil { + server.fallbacks = make(map[string]map[string]*Fallback) + for _, fb := range config.Fallbacks { + if server.fallbacks[fb.Alpn] == nil { + server.fallbacks[fb.Alpn] = make(map[string]*Fallback) + } + server.fallbacks[fb.Alpn][fb.Path] = fb + } + if server.fallbacks[""] != nil { + for alpn, pfb := range server.fallbacks { + if alpn != "" { // && alpn != "h2" { + for path, fb := range server.fallbacks[""] { + if pfb[path] == nil { + pfb[path] = fb + } + } + } + } + } } return server, nil @@ -69,54 +92,68 @@ func (s *Server) Network() []net.Network { // Process implements proxy.Inbound.Process(). func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { // nolint: funlen,lll + + sid := session.ExportIDToError(ctx) + + iConn := conn + if statConn, ok := iConn.(*internet.StatCouterConnection); ok { + iConn = statConn.Connection + } + sessionPolicy := s.policyManager.ForLevel(0) if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { return newError("unable to set read deadline").Base(err).AtWarning() } - buffer := buf.New() - defer buffer.Release() + first := buf.New() + defer first.Release() - n, err := buffer.ReadFrom(conn) + firstLen, err := first.ReadFrom(conn) if err != nil { return newError("failed to read first request").Base(err) } + newError("firstLen = ", firstLen).AtInfo().WriteToLog(sid) bufferedReader := &buf.BufferedReader{ Reader: buf.NewReader(conn), - Buffer: buf.MultiBuffer{buffer}, + Buffer: buf.MultiBuffer{first}, } var user *protocol.MemoryUser - fallbackEnabled := s.config.Fallback != nil + + apfb := s.fallbacks + isfb := apfb != nil + shouldFallback := false - if n < 56 { // nolint: gomnd + if firstLen < 58 || first.Byte(56) != '\r' { // nolint: gomnd // invalid protocol + err = newError("not trojan protocol") log.Record(&log.AccessMessage{ From: conn.RemoteAddr(), To: "", Status: log.AccessRejected, - Reason: newError("not trojan protocol"), + Reason: err, }) shouldFallback = true } else { - user = s.validator.Get(hexString(buffer.BytesTo(56))) // nolint: gomnd + user = s.validator.Get(hexString(first.BytesTo(56))) // nolint: gomnd if user == nil { // invalid user, let's fallback + err = newError("not a valid user") log.Record(&log.AccessMessage{ From: conn.RemoteAddr(), To: "", Status: log.AccessRejected, - Reason: newError("not a valid user"), + Reason: err, }) shouldFallback = true } } - if fallbackEnabled && shouldFallback { - return s.fallback(ctx, sessionPolicy, bufferedReader, buf.NewWriter(conn)) + if isfb && shouldFallback { + return s.fallback(ctx, sid, err, sessionPolicy, conn, iConn, apfb, first, firstLen, bufferedReader) } else if shouldFallback { return newError("invalid protocol or invalid user") } @@ -158,7 +195,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn internet Email: user.Email, }) - newError("received request for ", destination).WriteToLog(session.ExportIDToError(ctx)) + newError("received request for ", destination).WriteToLog(sid) return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher) } @@ -240,15 +277,70 @@ func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Sess return nil } -func (s *Server) fallback(ctx context.Context, sessionPolicy policy.Session, requestReader buf.Reader, responseWriter buf.Writer) error { // nolint: lll +func (s *Server) fallback(ctx context.Context, sid errors.ExportOption, err error, sessionPolicy policy.Session, connection internet.Connection, iConn internet.Connection, apfb map[string]map[string]*Fallback, first *buf.Buffer, firstLen int64, reader buf.Reader) error { // nolint: lll + if err := connection.SetReadDeadline(time.Time{}); err != nil { + newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid) + } + newError("fallback starts").Base(err).AtInfo().WriteToLog(sid) + + alpn := "" + if len(apfb) > 1 || apfb[""] == nil { + if tlsConn, ok := iConn.(*tls.Conn); ok { + alpn = tlsConn.ConnectionState().NegotiatedProtocol + newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) + } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { + alpn = xtlsConn.ConnectionState().NegotiatedProtocol + newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) + } + if apfb[alpn] == nil { + alpn = "" + } + } + pfb := apfb[alpn] + if pfb == nil { + return newError(`failed to find the default "alpn" config`).AtWarning() + } + + path := "" + if len(pfb) > 1 || pfb[""] == nil { + if firstLen >= 18 && first.Byte(4) != '*' { // not h2c + firstBytes := first.Bytes() + for i := 4; i <= 8; i++ { // 5 -> 9 + if firstBytes[i] == '/' && firstBytes[i-1] == ' ' { + search := len(firstBytes) + if search > 64 { + search = 64 // up to about 60 + } + for j := i + 1; j < search; j++ { + k := firstBytes[j] + if k == '\r' || k == '\n' { // avoid logging \r or \n + break + } + if k == ' ' { + path = string(firstBytes[i:j]) + newError("realPath = " + path).AtInfo().WriteToLog(sid) + if pfb[path] == nil { + path = "" + } + break + } + } + break + } + } + } + } + fb := pfb[path] + if fb == nil { + return newError(`failed to find the default "path" config`).AtWarning() + } + ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) var conn net.Conn - var err error - fb := s.config.Fallback - if err := retry.ExponentialBackoff(5, 100).On(func() error { // nolint: gomnd + if err := retry.ExponentialBackoff(5, 100).On(func() error { var dialer net.Dialer conn, err = dialer.DialContext(ctx, fb.Type, fb.Dest) if err != nil { @@ -263,24 +355,69 @@ func (s *Server) fallback(ctx context.Context, sessionPolicy policy.Session, req serverReader := buf.NewReader(conn) serverWriter := buf.NewWriter(conn) - requestDone := func() error { + postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) - - if err := buf.Copy(requestReader, serverWriter, buf.UpdateActivity(timer)); err != nil { + if fb.Xver != 0 { + remoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String()) + if err != nil { + return err + } + localAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String()) + if err != nil { + return err + } + ipv4 := true + for i := 0; i < len(remoteAddr); i++ { + if remoteAddr[i] == ':' { + ipv4 = false + break + } + } + pro := buf.New() + defer pro.Release() + switch fb.Xver { + case 1: + if ipv4 { + common.Must2(pro.Write([]byte("PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"))) + } else { + common.Must2(pro.Write([]byte("PROXY TCP6 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"))) + } + case 2: + common.Must2(pro.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21"))) // signature + v2 + PROXY + if ipv4 { + common.Must2(pro.Write([]byte("\x11\x00\x0C"))) // AF_INET + STREAM + 12 bytes + common.Must2(pro.Write(net.ParseIP(remoteAddr).To4())) + common.Must2(pro.Write(net.ParseIP(localAddr).To4())) + } else { + common.Must2(pro.Write([]byte("\x21\x00\x24"))) // AF_INET6 + STREAM + 36 bytes + common.Must2(pro.Write(net.ParseIP(remoteAddr).To16())) + common.Must2(pro.Write(net.ParseIP(localAddr).To16())) + } + p1, _ := strconv.ParseUint(remotePort, 10, 16) + p2, _ := strconv.ParseUint(localPort, 10, 16) + common.Must2(pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)})) + } + if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil { + return newError("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning() + } + } + if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to fallback request payload").Base(err).AtInfo() } return nil } - responseDone := func() error { + writer := buf.NewWriter(connection) + + getResponse := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) - if err := buf.Copy(serverReader, responseWriter, buf.UpdateActivity(timer)); err != nil { + if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to deliver response payload").Base(err).AtInfo() } return nil } - if err := task.Run(ctx, task.OnSuccess(requestDone, task.Close(serverWriter)), task.OnSuccess(responseDone, task.Close(responseWriter))); err != nil { // nolint: lll + if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil { common.Must(common.Interrupt(serverReader)) common.Must(common.Interrupt(serverWriter)) return newError("fallback ends").Base(err).AtInfo()