From c9b6fc01046f59d25ba4274020f09da8e8d94c36 Mon Sep 17 00:00:00 2001 From: PMExtra Date: Thu, 15 Dec 2022 19:15:43 +0800 Subject: [PATCH] Add custom header support for HTTP proxy --- infra/conf/http.go | 8 +++ proxy/http/client.go | 51 ++++++++++++++- proxy/http/config.pb.go | 135 ++++++++++++++++++++++++++++++++-------- proxy/http/config.proto | 6 ++ 4 files changed, 172 insertions(+), 28 deletions(-) diff --git a/infra/conf/http.go b/infra/conf/http.go index c917197c..ddeaa69e 100644 --- a/infra/conf/http.go +++ b/infra/conf/http.go @@ -53,6 +53,7 @@ type HTTPRemoteConfig struct { type HTTPClientConfig struct { Servers []*HTTPRemoteConfig `json:"servers"` + Headers map[string]string `json:"headers"` } func (v *HTTPClientConfig) Build() (proto.Message, error) { @@ -77,5 +78,12 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) { } config.Server[idx] = server } + config.Header = make([]*http.Header, 0, 32) + for key, value := range v.Headers { + config.Header = append(config.Header, &http.Header{ + Key: key, + Value: value, + }) + } return config, nil } diff --git a/proxy/http/client.go b/proxy/http/client.go index ae80e354..71a10e69 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -2,12 +2,14 @@ package http import ( "bufio" + "bytes" "context" "encoding/base64" "io" "net/http" "net/url" "sync" + "text/template" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" @@ -30,6 +32,7 @@ import ( type Client struct { serverPicker protocol.ServerPicker policyManager policy.Manager + header []*Header } type h2Conn struct { @@ -60,6 +63,7 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { return &Client{ serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + header: config.Header, }, nil } @@ -88,12 +92,17 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter buf.ReleaseMulti(mbuf) defer bytespool.Free(firstPayload) + header, err := fillRequestHeader(ctx, c.header) + if err != nil { + return newError("failed to fill out header").Base(err) + } + if err := retry.ExponentialBackoff(5, 100).On(func() error { server := c.serverPicker.PickServer() dest := server.Destination() user = server.PickUser() - netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload) + netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, header, firstPayload) if netConn != nil { if _, ok := netConn.(*http2Conn); !ok { if _, err := netConn.Write(firstPayload); err != nil { @@ -139,8 +148,42 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return nil } +// fillRequestHeader will fill out the template of the headers +func fillRequestHeader(ctx context.Context, header []*Header) ([]*Header, error) { + if len(header) == 0 { + return header, nil + } + + inbound := session.InboundFromContext(ctx) + outbound := session.OutboundFromContext(ctx) + + data := struct { + Source net.Destination + Target net.Destination + }{ + Source: inbound.Source, + Target: outbound.Target, + } + + filled := make([]*Header, len(header)) + for i, h := range header { + tmpl, err := template.New(h.Key).Parse(h.Value) + if err != nil { + return nil, err + } + var buf bytes.Buffer + + if err = tmpl.Execute(&buf, data); err != nil { + return nil, err + } + filled[i] = &Header{Key: h.Key, Value: buf.String()} + } + + return filled, nil +} + // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method -func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) { +func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, header []*Header, firstPayload []byte) (net.Conn, error) { req := &http.Request{ Method: http.MethodConnect, URL: &url.URL{Host: target}, @@ -154,6 +197,10 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) } + for _, h := range header { + req.Header.Set(h.Key, h.Value) + } + connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") diff --git a/proxy/http/config.pb.go b/proxy/http/config.pb.go index de1b916a..e2613cda 100644 --- a/proxy/http/config.pb.go +++ b/proxy/http/config.pb.go @@ -150,6 +150,61 @@ func (x *ServerConfig) GetUserLevel() uint32 { return 0 } +type Header struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Header) Reset() { + *x = Header{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_http_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Header) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Header) ProtoMessage() {} + +func (x *Header) ProtoReflect() protoreflect.Message { + mi := &file_proxy_http_config_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Header.ProtoReflect.Descriptor instead. +func (*Header) Descriptor() ([]byte, []int) { + return file_proxy_http_config_proto_rawDescGZIP(), []int{2} +} + +func (x *Header) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Header) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + // ClientConfig is the protobuf config for HTTP proxy client. type ClientConfig struct { state protoimpl.MessageState @@ -158,12 +213,13 @@ type ClientConfig struct { // Sever is a list of HTTP server addresses. Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` + Header []*Header `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty"` } func (x *ClientConfig) Reset() { *x = ClientConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proxy_http_config_proto_msgTypes[2] + mi := &file_proxy_http_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -176,7 +232,7 @@ func (x *ClientConfig) String() string { func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { - mi := &file_proxy_http_config_proto_msgTypes[2] + mi := &file_proxy_http_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -189,7 +245,7 @@ func (x *ClientConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { - return file_proxy_http_config_proto_rawDescGZIP(), []int{2} + return file_proxy_http_config_proto_rawDescGZIP(), []int{3} } func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { @@ -199,6 +255,13 @@ func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { return nil } +func (x *ClientConfig) GetHeader() []*Header { + if x != nil { + return x.Header + } + return nil +} + var File_proxy_http_config_proto protoreflect.FileDescriptor var file_proxy_http_config_proto_rawDesc = []byte{ @@ -227,17 +290,23 @@ var file_proxy_http_config_proto_rawDesc = []byte{ 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x4c, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 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, 0x42, - 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, - 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, - 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48, 0x74, 0x74, 0x70, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x01, 0x22, 0x30, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x7d, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 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, 0x12, 0x2f, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, + 0x74, 0x74, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, + 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, + 0x70, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48, + 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -252,22 +321,24 @@ func file_proxy_http_config_proto_rawDescGZIP() []byte { return file_proxy_http_config_proto_rawDescData } -var file_proxy_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_proxy_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_proxy_http_config_proto_goTypes = []interface{}{ (*Account)(nil), // 0: xray.proxy.http.Account (*ServerConfig)(nil), // 1: xray.proxy.http.ServerConfig - (*ClientConfig)(nil), // 2: xray.proxy.http.ClientConfig - nil, // 3: xray.proxy.http.ServerConfig.AccountsEntry - (*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint + (*Header)(nil), // 2: xray.proxy.http.Header + (*ClientConfig)(nil), // 3: xray.proxy.http.ClientConfig + nil, // 4: xray.proxy.http.ServerConfig.AccountsEntry + (*protocol.ServerEndpoint)(nil), // 5: xray.common.protocol.ServerEndpoint } var file_proxy_http_config_proto_depIdxs = []int32{ - 3, // 0: xray.proxy.http.ServerConfig.accounts:type_name -> xray.proxy.http.ServerConfig.AccountsEntry - 4, // 1: xray.proxy.http.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 4, // 0: xray.proxy.http.ServerConfig.accounts:type_name -> xray.proxy.http.ServerConfig.AccountsEntry + 5, // 1: xray.proxy.http.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint + 2, // 2: xray.proxy.http.ClientConfig.header:type_name -> xray.proxy.http.Header + 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 + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_proxy_http_config_proto_init() } @@ -301,6 +372,18 @@ func file_proxy_http_config_proto_init() { } } file_proxy_http_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Header); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proxy_http_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClientConfig); i { case 0: return &v.state @@ -319,7 +402,7 @@ func file_proxy_http_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_http_config_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/proxy/http/config.proto b/proxy/http/config.proto index bcb11ebf..743c8551 100644 --- a/proxy/http/config.proto +++ b/proxy/http/config.proto @@ -21,8 +21,14 @@ message ServerConfig { uint32 user_level = 4; } +message Header { + string key = 1; + string value = 2; +} + // ClientConfig is the protobuf config for HTTP proxy client. message ClientConfig { // Sever is a list of HTTP server addresses. repeated xray.common.protocol.ServerEndpoint server = 1; + repeated Header header = 2; }