mirror of https://github.com/XTLS/Xray-core
				
				
				
			Add custom header support for HTTP proxy
							parent
							
								
									d7ac6946d2
								
							
						
					
					
						commit
						c9b6fc0104
					
				|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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") | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
| 		}, | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 PMExtra
						PMExtra