diff --git a/infra/conf/http.go b/infra/conf/http.go index 5b7876330..d17537244 100644 --- a/infra/conf/http.go +++ b/infra/conf/http.go @@ -1,7 +1,11 @@ package conf import ( + "encoding/json" + "github.com/golang/protobuf/proto" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/serial" "v2ray.com/core/proxy/http" ) @@ -10,6 +14,13 @@ type HttpAccount struct { Password string `json:"pass"` } +func (v *HttpAccount) Build() *http.Account { + return &http.Account{ + Username: v.Username, + Password: v.Password, + } +} + type HttpServerConfig struct { Timeout uint32 `json:"timeout"` Accounts []*HttpAccount `json:"accounts"` @@ -33,3 +44,37 @@ func (c *HttpServerConfig) Build() (proto.Message, error) { return config, nil } + +type HttpRemoteConfig struct { + Address *Address `json:"address"` + Port uint16 `json:"port"` + Users []json.RawMessage `json:"users"` +} +type HttpClientConfig struct { + Servers []*HttpRemoteConfig `json:"servers"` +} + +func (v *HttpClientConfig) Build() (proto.Message, error) { + config := new(http.ClientConfig) + config.Server = make([]*protocol.ServerEndpoint, len(v.Servers)) + for idx, serverConfig := range v.Servers { + server := &protocol.ServerEndpoint{ + Address: serverConfig.Address.Build(), + Port: uint32(serverConfig.Port), + } + for _, rawUser := range serverConfig.Users { + user := new(protocol.User) + if err := json.Unmarshal(rawUser, user); err != nil { + return nil, newError("failed to parse HTTP user").Base(err).AtError() + } + account := new(HttpAccount) + if err := json.Unmarshal(rawUser, account); err != nil { + return nil, newError("failed to parse HTTP account").Base(err).AtError() + } + user.Account = serial.ToTypedMessage(account.Build()) + server.User = append(server.User, user) + } + config.Server[idx] = server + } + return config, nil +} diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index a937d780b..3d3c0d112 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -24,6 +24,7 @@ var ( outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ "blackhole": func() interface{} { return new(BlackholeConfig) }, "freedom": func() interface{} { return new(FreedomConfig) }, + "http": func() interface{} { return new(HttpClientConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) }, "socks": func() interface{} { return new(SocksClientConfig) }, diff --git a/proxy/http/client.go b/proxy/http/client.go index 347f59429..fcb13189f 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -1,6 +1,147 @@ package http -/* +import ( + "context" + "encoding/base64" + "io" + "strings" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/retry" + "v2ray.com/core/common/session" + "v2ray.com/core/common/signal" + "v2ray.com/core/common/task" + "v2ray.com/core/features/policy" + "v2ray.com/core/transport" + "v2ray.com/core/transport/internet" +) + type Client struct { + serverPicker protocol.ServerPicker + policyManager policy.Manager +} + +// NewClient create a new http client based on the given config. +func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { + serverList := protocol.NewServerList() + for _, rec := range config.Server { + s, err := protocol.NewServerSpecFromPB(*rec) + if err != nil { + return nil, newError("failed to get server spec").Base(err) + } + serverList.AddServer(s) + } + if serverList.Size() == 0 { + return nil, newError("0 target server") + } + + v := core.MustFromContext(ctx) + return &Client{ + serverPicker: protocol.NewRoundRobinServerPicker(serverList), + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + }, nil +} + +// Process implements proxy.Outbound.Process. +// 使用connect方法连接http代理服务器,获得一个隧道,然后通过该隧道通信 +func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified.") + } + destination := outbound.Target + + if destination.Network == net.Network_UDP { + return newError("UDP is not supported by HTTP outbound") + } + + var server *protocol.ServerSpec + var conn internet.Connection + + if err := retry.ExponentialBackoff(5, 100).On(func() error { + server = c.serverPicker.PickServer() + dest := server.Destination() + rawConn, err := dialer.Dial(ctx, dest) + if err != nil { + return err + } + conn = rawConn + + return nil + }); err != nil { + return newError("failed to find an available destination").Base(err) + } + + defer func() { + if err := conn.Close(); err != nil { + newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + }() + + 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) + + requestFunc := func() error { + defer timer.SetTimeout(p.Timeouts.DownlinkOnly) + return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) + } + responseFunc := func() error { + defer timer.SetTimeout(p.Timeouts.UplinkOnly) + return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) + } + + var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer)) + if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { + return newError("connection ends").Base(err) + } + + return nil +} + +// 使用http connect方法建立一个隧道 +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) + 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))) + } + headers = append(headers, "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 + } + + b.Clear() + if _, err := b.ReadFrom(reader); err != nil { + return err + } + + return nil +} + +func init() { + common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return NewClient(ctx, config.(*ClientConfig)) + })) } -*/ diff --git a/proxy/http/config.go b/proxy/http/config.go index cbe486d5a..7e8da149d 100644 --- a/proxy/http/config.go +++ b/proxy/http/config.go @@ -1,5 +1,20 @@ package http +import ( + "v2ray.com/core/common/protocol" +) + +func (a *Account) Equals(another protocol.Account) bool { + if account, ok := another.(*Account); ok { + return a.Username == account.Username + } + return false +} + +func (a *Account) AsAccount() (protocol.Account, error) { + return a, nil +} + func (sc *ServerConfig) HasAccount(username, password string) bool { if sc.Accounts == nil { return false diff --git a/proxy/http/config.pb.go b/proxy/http/config.pb.go index 691aa6f1a..a7b2507ee 100644 --- a/proxy/http/config.pb.go +++ b/proxy/http/config.pb.go @@ -4,6 +4,7 @@ import ( fmt "fmt" proto "github.com/golang/protobuf/proto" math "math" + protocol "v2ray.com/core/common/protocol" ) // Reference imports to suppress errors if they are not otherwise used. @@ -17,6 +18,53 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +type Account struct { + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Account) Reset() { *m = Account{} } +func (m *Account) String() string { return proto.CompactTextString(m) } +func (*Account) ProtoMessage() {} +func (*Account) Descriptor() ([]byte, []int) { + return fileDescriptor_e66c3db3a635d8e4, []int{0} +} + +func (m *Account) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Account.Unmarshal(m, b) +} +func (m *Account) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Account.Marshal(b, m, deterministic) +} +func (m *Account) XXX_Merge(src proto.Message) { + xxx_messageInfo_Account.Merge(m, src) +} +func (m *Account) XXX_Size() int { + return xxx_messageInfo_Account.Size(m) +} +func (m *Account) XXX_DiscardUnknown() { + xxx_messageInfo_Account.DiscardUnknown(m) +} + +var xxx_messageInfo_Account proto.InternalMessageInfo + +func (m *Account) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + +func (m *Account) GetPassword() string { + if m != nil { + return m.Password + } + return "" +} + // Config for HTTP proxy server. type ServerConfig struct { Timeout uint32 `protobuf:"varint,1,opt,name=timeout,proto3" json:"timeout,omitempty"` // Deprecated: Do not use. @@ -32,7 +80,7 @@ func (m *ServerConfig) Reset() { *m = ServerConfig{} } func (m *ServerConfig) String() string { return proto.CompactTextString(m) } func (*ServerConfig) ProtoMessage() {} func (*ServerConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_e66c3db3a635d8e4, []int{0} + return fileDescriptor_e66c3db3a635d8e4, []int{1} } func (m *ServerConfig) XXX_Unmarshal(b []byte) error { @@ -82,18 +130,20 @@ func (m *ServerConfig) GetUserLevel() uint32 { return 0 } -// ClientConfig for HTTP proxy client. +// ClientConfig is the protobuf config for HTTP proxy client. type ClientConfig struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // Sever is a list of HTTP server addresses. + Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ClientConfig) Reset() { *m = ClientConfig{} } func (m *ClientConfig) String() string { return proto.CompactTextString(m) } func (*ClientConfig) ProtoMessage() {} func (*ClientConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_e66c3db3a635d8e4, []int{1} + return fileDescriptor_e66c3db3a635d8e4, []int{2} } func (m *ClientConfig) XXX_Unmarshal(b []byte) error { @@ -114,7 +164,15 @@ func (m *ClientConfig) XXX_DiscardUnknown() { var xxx_messageInfo_ClientConfig proto.InternalMessageInfo +func (m *ClientConfig) GetServer() []*protocol.ServerEndpoint { + if m != nil { + return m.Server + } + return nil +} + func init() { + proto.RegisterType((*Account)(nil), "v2ray.core.proxy.http.Account") proto.RegisterType((*ServerConfig)(nil), "v2ray.core.proxy.http.ServerConfig") proto.RegisterMapType((map[string]string)(nil), "v2ray.core.proxy.http.ServerConfig.AccountsEntry") proto.RegisterType((*ClientConfig)(nil), "v2ray.core.proxy.http.ClientConfig") @@ -125,24 +183,29 @@ func init() { } var fileDescriptor_e66c3db3a635d8e4 = []byte{ - // 296 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xcf, 0x4a, 0x33, 0x31, - 0x14, 0xc5, 0x99, 0x69, 0xbf, 0xcf, 0xf6, 0xda, 0x4a, 0x0d, 0x16, 0x46, 0x51, 0x28, 0x5d, 0x48, - 0x41, 0xc8, 0x60, 0xdd, 0x88, 0x5d, 0xd9, 0x22, 0xb8, 0x50, 0x28, 0x51, 0x5c, 0xb8, 0x29, 0x31, - 0x5c, 0xb5, 0x98, 0x26, 0x43, 0xe6, 0xce, 0xe8, 0xec, 0x7d, 0x1a, 0x9f, 0x52, 0x92, 0x5a, 0xff, - 0x40, 0x57, 0x49, 0x7e, 0xe7, 0xe4, 0xe4, 0x9e, 0xc0, 0x61, 0x39, 0x74, 0xb2, 0xe2, 0xca, 0x2e, - 0x52, 0x65, 0x1d, 0xa6, 0x99, 0xb3, 0x6f, 0x55, 0xfa, 0x4c, 0x94, 0xa5, 0xca, 0x9a, 0xc7, 0xf9, - 0x13, 0xcf, 0x9c, 0x25, 0xcb, 0xba, 0x2b, 0x9f, 0x43, 0x1e, 0x3c, 0xdc, 0x7b, 0xfa, 0xef, 0x31, - 0xb4, 0x6e, 0xd0, 0x95, 0xe8, 0x26, 0xc1, 0xcd, 0xf6, 0x61, 0x83, 0xe6, 0x0b, 0xb4, 0x05, 0x25, - 0x51, 0x2f, 0x1a, 0xb4, 0xc7, 0x71, 0x12, 0x89, 0x15, 0x62, 0xd7, 0xd0, 0x90, 0x4a, 0xd9, 0xc2, - 0x50, 0x9e, 0xc4, 0xbd, 0xda, 0x60, 0x73, 0x78, 0xcc, 0xd7, 0x06, 0xf3, 0xdf, 0xa1, 0xfc, 0xfc, - 0xeb, 0xce, 0x85, 0x21, 0x57, 0x89, 0xef, 0x08, 0x76, 0x04, 0xdb, 0x52, 0x6b, 0xfb, 0x3a, 0x23, - 0x27, 0x4d, 0x9e, 0x49, 0x87, 0x86, 0x92, 0x5a, 0x2f, 0x1a, 0x34, 0x44, 0x27, 0x08, 0xb7, 0x3f, - 0x9c, 0x1d, 0x00, 0x14, 0x39, 0xba, 0x99, 0xc6, 0x12, 0x75, 0x52, 0xf7, 0xc3, 0x89, 0xa6, 0x27, - 0x57, 0x1e, 0xec, 0x8d, 0xa0, 0xfd, 0xe7, 0x19, 0xd6, 0x81, 0xda, 0x0b, 0x56, 0xa1, 0x45, 0x53, - 0xf8, 0x2d, 0xdb, 0x81, 0x7f, 0xa5, 0xd4, 0x05, 0x26, 0x71, 0x60, 0xcb, 0xc3, 0x59, 0x7c, 0x1a, - 0xf5, 0xb7, 0xa0, 0x35, 0xd1, 0x73, 0x34, 0xb4, 0x1c, 0x78, 0x3c, 0x82, 0x5d, 0x65, 0x17, 0xeb, - 0xab, 0x4d, 0xa3, 0xfb, 0xba, 0x5f, 0x3f, 0xe2, 0xee, 0xdd, 0x50, 0xc8, 0x8a, 0x4f, 0xbc, 0x3e, - 0x0d, 0xfa, 0x25, 0x51, 0xf6, 0xf0, 0x3f, 0xfc, 0xf8, 0xc9, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x69, 0x94, 0x9f, 0xa7, 0x9b, 0x01, 0x00, 0x00, + // 375 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0x4d, 0x6b, 0xe3, 0x30, + 0x10, 0xc5, 0x4e, 0x36, 0x1f, 0xda, 0x04, 0xb2, 0x62, 0x03, 0xde, 0xb0, 0x0b, 0x21, 0x87, 0x25, + 0xb4, 0x20, 0xb7, 0xe9, 0xa5, 0x34, 0xa7, 0x24, 0x04, 0x7a, 0x68, 0x21, 0xa8, 0xa5, 0x87, 0x5e, + 0x82, 0xaa, 0xa8, 0xad, 0xa9, 0x2d, 0x09, 0x49, 0x76, 0xea, 0x7b, 0x7f, 0x4d, 0x7f, 0x65, 0x91, + 0x6c, 0xa7, 0x69, 0xc9, 0xc9, 0x9e, 0xf7, 0x66, 0x9e, 0xe6, 0xbd, 0x01, 0xff, 0xb3, 0x89, 0x22, + 0x39, 0xa2, 0x22, 0x09, 0xa9, 0x50, 0x2c, 0x94, 0x4a, 0xbc, 0xe6, 0xe1, 0xb3, 0x31, 0x32, 0xa4, + 0x82, 0x3f, 0x46, 0x4f, 0x48, 0x2a, 0x61, 0x04, 0xec, 0x57, 0x7d, 0x8a, 0x21, 0xd7, 0x83, 0x6c, + 0xcf, 0xe0, 0xe4, 0xdb, 0x38, 0x15, 0x49, 0x22, 0x78, 0xe8, 0x66, 0xa8, 0x88, 0x43, 0xcd, 0x54, + 0xc6, 0xd4, 0x5a, 0x4b, 0x46, 0x0b, 0xa1, 0xd1, 0x0c, 0x34, 0x67, 0x94, 0x8a, 0x94, 0x1b, 0x38, + 0x00, 0xad, 0x54, 0x33, 0xc5, 0x49, 0xc2, 0x02, 0x6f, 0xe8, 0x8d, 0xdb, 0x78, 0x57, 0x5b, 0x4e, + 0x12, 0xad, 0xb7, 0x42, 0x6d, 0x02, 0xbf, 0xe0, 0xaa, 0x7a, 0xf4, 0xe6, 0x83, 0xce, 0x8d, 0x13, + 0x5e, 0xb8, 0x15, 0xe1, 0x5f, 0xd0, 0x34, 0x51, 0xc2, 0x44, 0x6a, 0x9c, 0x4e, 0x77, 0xee, 0x07, + 0x1e, 0xae, 0x20, 0x78, 0x0d, 0x5a, 0xa4, 0x78, 0x51, 0x07, 0xfe, 0xb0, 0x36, 0xfe, 0x39, 0x39, + 0x45, 0x07, 0xdd, 0xa0, 0x7d, 0x51, 0x54, 0x6e, 0xa9, 0x97, 0xdc, 0xa8, 0x1c, 0xef, 0x24, 0xe0, + 0x31, 0xf8, 0x45, 0xe2, 0x58, 0x6c, 0xd7, 0x46, 0x11, 0xae, 0x25, 0x51, 0x8c, 0x9b, 0xa0, 0x36, + 0xf4, 0xc6, 0x2d, 0xdc, 0x73, 0xc4, 0xed, 0x27, 0x0e, 0xff, 0x01, 0x60, 0x2d, 0xad, 0x63, 0x96, + 0xb1, 0x38, 0xa8, 0xdb, 0xe5, 0x70, 0xdb, 0x22, 0x57, 0x16, 0x18, 0x4c, 0x41, 0xf7, 0xcb, 0x33, + 0xb0, 0x07, 0x6a, 0x2f, 0x2c, 0x2f, 0xd3, 0xb0, 0xbf, 0xf0, 0x37, 0xf8, 0x91, 0x91, 0x38, 0x65, + 0x65, 0x0a, 0x45, 0x71, 0xe1, 0x9f, 0x7b, 0x23, 0x0c, 0x3a, 0x8b, 0x38, 0x62, 0xdc, 0x94, 0x29, + 0xcc, 0x41, 0xa3, 0x88, 0x3b, 0xf0, 0x9c, 0xcb, 0xa3, 0x7d, 0x97, 0xc5, 0x61, 0x50, 0x75, 0x98, + 0xd2, 0xea, 0x92, 0x6f, 0xa4, 0x88, 0xb8, 0xc1, 0xe5, 0xe4, 0x7c, 0x0a, 0xfe, 0x50, 0x91, 0x1c, + 0x8e, 0x67, 0xe5, 0xdd, 0xd7, 0xed, 0xf7, 0xdd, 0xef, 0xdf, 0x4d, 0x30, 0xc9, 0xd1, 0xc2, 0xf2, + 0x2b, 0xc7, 0x5f, 0x1a, 0x23, 0x1f, 0x1a, 0x4e, 0xfd, 0xec, 0x23, 0x00, 0x00, 0xff, 0xff, 0xac, + 0x7a, 0x67, 0x04, 0x54, 0x02, 0x00, 0x00, } diff --git a/proxy/http/config.proto b/proxy/http/config.proto index 995737dd7..7ab5d8582 100644 --- a/proxy/http/config.proto +++ b/proxy/http/config.proto @@ -6,6 +6,13 @@ option go_package = "http"; option java_package = "com.v2ray.core.proxy.http"; option java_multiple_files = true; +import "v2ray.com/core/common/protocol/server_spec.proto"; + +message Account { + string username = 1; + string password = 2; +} + // Config for HTTP proxy server. message ServerConfig { uint32 timeout = 1 [deprecated = true]; @@ -14,7 +21,8 @@ message ServerConfig { uint32 user_level = 4; } -// ClientConfig for HTTP proxy client. +// ClientConfig is the protobuf config for HTTP proxy client. message ClientConfig { - + // Sever is a list of HTTP server addresses. + repeated v2ray.core.common.protocol.ServerEndpoint server = 1; }