mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-29 16:58:34 +00:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
711 changed files with 82154 additions and 2 deletions
154
proxy/socks/client.go
Normal file
154
proxy/socks/client.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
// +build !confonly
|
||||
|
||||
package socks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/common/protocol"
|
||||
"github.com/xtls/xray-core/v1/common/retry"
|
||||
"github.com/xtls/xray-core/v1/common/session"
|
||||
"github.com/xtls/xray-core/v1/common/signal"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/features/policy"
|
||||
"github.com/xtls/xray-core/v1/transport"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
)
|
||||
|
||||
// Client is a Socks5 client.
|
||||
type Client struct {
|
||||
serverPicker protocol.ServerPicker
|
||||
policyManager policy.Manager
|
||||
}
|
||||
|
||||
// NewClient create a new Socks5 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.
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
request := &protocol.RequestHeader{
|
||||
Version: socks5Version,
|
||||
Command: protocol.RequestCommandTCP,
|
||||
Address: destination.Address,
|
||||
Port: destination.Port,
|
||||
}
|
||||
if destination.Network == net.Network_UDP {
|
||||
request.Command = protocol.RequestCommandUDP
|
||||
}
|
||||
|
||||
user := server.PickUser()
|
||||
if user != nil {
|
||||
request.User = user
|
||||
p = c.policyManager.ForLevel(user.Level)
|
||||
}
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(p.Timeouts.Handshake)); err != nil {
|
||||
newError("failed to set deadline for handshake").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
udpRequest, err := ClientHandshake(request, conn, conn)
|
||||
if err != nil {
|
||||
return newError("failed to establish connection to server").AtWarning().Base(err)
|
||||
}
|
||||
|
||||
if err := conn.SetDeadline(time.Time{}); err != nil {
|
||||
newError("failed to clear deadline after handshake").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle)
|
||||
|
||||
var requestFunc func() error
|
||||
var responseFunc func() error
|
||||
if request.Command == protocol.RequestCommandTCP {
|
||||
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))
|
||||
}
|
||||
} else if request.Command == protocol.RequestCommandUDP {
|
||||
udpConn, err := dialer.Dial(ctx, udpRequest.Destination())
|
||||
if err != nil {
|
||||
return newError("failed to create UDP connection").Base(err)
|
||||
}
|
||||
defer udpConn.Close()
|
||||
requestFunc = func() error {
|
||||
defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
|
||||
return buf.Copy(link.Reader, &buf.SequentialWriter{Writer: NewUDPWriter(request, udpConn)}, buf.UpdateActivity(timer))
|
||||
}
|
||||
responseFunc = func() error {
|
||||
defer timer.SetTimeout(p.Timeouts.UplinkOnly)
|
||||
reader := &UDPReader{reader: udpConn}
|
||||
return buf.Copy(reader, 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
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return NewClient(ctx, config.(*ClientConfig))
|
||||
}))
|
||||
}
|
27
proxy/socks/config.go
Normal file
27
proxy/socks/config.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
// +build !confonly
|
||||
|
||||
package socks
|
||||
|
||||
import "github.com/xtls/xray-core/v1/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 (c *ServerConfig) HasAccount(username, password string) bool {
|
||||
if c.Accounts == nil {
|
||||
return false
|
||||
}
|
||||
storedPassed, found := c.Accounts[username]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
return storedPassed == password
|
||||
}
|
423
proxy/socks/config.pb.go
Normal file
423
proxy/socks/config.pb.go
Normal file
|
@ -0,0 +1,423 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.14.0
|
||||
// source: proxy/socks/config.proto
|
||||
|
||||
package socks
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
net "github.com/xtls/xray-core/v1/common/net"
|
||||
protocol "github.com/xtls/xray-core/v1/common/protocol"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
// AuthType is the authentication type of Socks proxy.
|
||||
type AuthType int32
|
||||
|
||||
const (
|
||||
// NO_AUTH is for anounymous authentication.
|
||||
AuthType_NO_AUTH AuthType = 0
|
||||
// PASSWORD is for username/password authentication.
|
||||
AuthType_PASSWORD AuthType = 1
|
||||
)
|
||||
|
||||
// Enum value maps for AuthType.
|
||||
var (
|
||||
AuthType_name = map[int32]string{
|
||||
0: "NO_AUTH",
|
||||
1: "PASSWORD",
|
||||
}
|
||||
AuthType_value = map[string]int32{
|
||||
"NO_AUTH": 0,
|
||||
"PASSWORD": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x AuthType) Enum() *AuthType {
|
||||
p := new(AuthType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x AuthType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (AuthType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_proxy_socks_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (AuthType) Type() protoreflect.EnumType {
|
||||
return &file_proxy_socks_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x AuthType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AuthType.Descriptor instead.
|
||||
func (AuthType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_proxy_socks_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// Account represents a Socks account.
|
||||
type Account struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Account) Reset() {
|
||||
*x = Account{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proxy_socks_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Account) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Account) ProtoMessage() {}
|
||||
|
||||
func (x *Account) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_socks_config_proto_msgTypes[0]
|
||||
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 Account.ProtoReflect.Descriptor instead.
|
||||
func (*Account) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_socks_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Account) GetUsername() string {
|
||||
if x != nil {
|
||||
return x.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Account) GetPassword() string {
|
||||
if x != nil {
|
||||
return x.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ServerConfig is the protobuf config for Socks server.
|
||||
type ServerConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
AuthType AuthType `protobuf:"varint,1,opt,name=auth_type,json=authType,proto3,enum=xray.proxy.socks.AuthType" json:"auth_type,omitempty"`
|
||||
Accounts map[string]string `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
Address *net.IPOrDomain `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
|
||||
UdpEnabled bool `protobuf:"varint,4,opt,name=udp_enabled,json=udpEnabled,proto3" json:"udp_enabled,omitempty"`
|
||||
// Deprecated: Do not use.
|
||||
Timeout uint32 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
|
||||
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ServerConfig) Reset() {
|
||||
*x = ServerConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proxy_socks_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ServerConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ServerConfig) ProtoMessage() {}
|
||||
|
||||
func (x *ServerConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_socks_config_proto_msgTypes[1]
|
||||
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 ServerConfig.ProtoReflect.Descriptor instead.
|
||||
func (*ServerConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_socks_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ServerConfig) GetAuthType() AuthType {
|
||||
if x != nil {
|
||||
return x.AuthType
|
||||
}
|
||||
return AuthType_NO_AUTH
|
||||
}
|
||||
|
||||
func (x *ServerConfig) GetAccounts() map[string]string {
|
||||
if x != nil {
|
||||
return x.Accounts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerConfig) GetAddress() *net.IPOrDomain {
|
||||
if x != nil {
|
||||
return x.Address
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerConfig) GetUdpEnabled() bool {
|
||||
if x != nil {
|
||||
return x.UdpEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Deprecated: Do not use.
|
||||
func (x *ServerConfig) GetTimeout() uint32 {
|
||||
if x != nil {
|
||||
return x.Timeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ServerConfig) GetUserLevel() uint32 {
|
||||
if x != nil {
|
||||
return x.UserLevel
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ClientConfig is the protobuf config for Socks client.
|
||||
type ClientConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Sever is a list of Socks server addresses.
|
||||
Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ClientConfig) Reset() {
|
||||
*x = ClientConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proxy_socks_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ClientConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ClientConfig) ProtoMessage() {}
|
||||
|
||||
func (x *ClientConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_socks_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 ClientConfig.ProtoReflect.Descriptor instead.
|
||||
func (*ClientConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_socks_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
|
||||
if x != nil {
|
||||
return x.Server
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proxy_socks_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proxy_socks_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x18, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x63, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x1a, 0x18, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73,
|
||||
0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x07, 0x41, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0xe3, 0x02, 0x0a,
|
||||
0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a,
|
||||
0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f,
|
||||
0x63, 0x6b, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x61, 0x75,
|
||||
0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x48, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
|
||||
0x12, 0x35, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07,
|
||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x64, 0x70, 0x5f, 0x65,
|
||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x64,
|
||||
0x70, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c,
|
||||
0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72,
|
||||
0x4c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x3b, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 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,
|
||||
0x2a, 0x25, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x4e, 0x4f, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x41, 0x53,
|
||||
0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x50,
|
||||
0x01, 0x5a, 0x28, 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, 0x76, 0x31, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x73, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_proxy_socks_config_proto_rawDescOnce sync.Once
|
||||
file_proxy_socks_config_proto_rawDescData = file_proxy_socks_config_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proxy_socks_config_proto_rawDescGZIP() []byte {
|
||||
file_proxy_socks_config_proto_rawDescOnce.Do(func() {
|
||||
file_proxy_socks_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_socks_config_proto_rawDescData)
|
||||
})
|
||||
return file_proxy_socks_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proxy_socks_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_proxy_socks_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_proxy_socks_config_proto_goTypes = []interface{}{
|
||||
(AuthType)(0), // 0: xray.proxy.socks.AuthType
|
||||
(*Account)(nil), // 1: xray.proxy.socks.Account
|
||||
(*ServerConfig)(nil), // 2: xray.proxy.socks.ServerConfig
|
||||
(*ClientConfig)(nil), // 3: xray.proxy.socks.ClientConfig
|
||||
nil, // 4: xray.proxy.socks.ServerConfig.AccountsEntry
|
||||
(*net.IPOrDomain)(nil), // 5: xray.common.net.IPOrDomain
|
||||
(*protocol.ServerEndpoint)(nil), // 6: xray.common.protocol.ServerEndpoint
|
||||
}
|
||||
var file_proxy_socks_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.proxy.socks.ServerConfig.auth_type:type_name -> xray.proxy.socks.AuthType
|
||||
4, // 1: xray.proxy.socks.ServerConfig.accounts:type_name -> xray.proxy.socks.ServerConfig.AccountsEntry
|
||||
5, // 2: xray.proxy.socks.ServerConfig.address:type_name -> xray.common.net.IPOrDomain
|
||||
6, // 3: xray.proxy.socks.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_socks_config_proto_init() }
|
||||
func file_proxy_socks_config_proto_init() {
|
||||
if File_proxy_socks_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proxy_socks_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Account); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proxy_socks_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ServerConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proxy_socks_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ClientConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_proxy_socks_config_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proxy_socks_config_proto_goTypes,
|
||||
DependencyIndexes: file_proxy_socks_config_proto_depIdxs,
|
||||
EnumInfos: file_proxy_socks_config_proto_enumTypes,
|
||||
MessageInfos: file_proxy_socks_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proxy_socks_config_proto = out.File
|
||||
file_proxy_socks_config_proto_rawDesc = nil
|
||||
file_proxy_socks_config_proto_goTypes = nil
|
||||
file_proxy_socks_config_proto_depIdxs = nil
|
||||
}
|
40
proxy/socks/config.proto
Normal file
40
proxy/socks/config.proto
Normal file
|
@ -0,0 +1,40 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.proxy.socks;
|
||||
option csharp_namespace = "Xray.Proxy.Socks";
|
||||
option go_package = "github.com/xtls/xray-core/v1/proxy/socks";
|
||||
option java_package = "com.xray.proxy.socks";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/net/address.proto";
|
||||
import "common/protocol/server_spec.proto";
|
||||
|
||||
// Account represents a Socks account.
|
||||
message Account {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
// AuthType is the authentication type of Socks proxy.
|
||||
enum AuthType {
|
||||
// NO_AUTH is for anounymous authentication.
|
||||
NO_AUTH = 0;
|
||||
// PASSWORD is for username/password authentication.
|
||||
PASSWORD = 1;
|
||||
}
|
||||
|
||||
// ServerConfig is the protobuf config for Socks server.
|
||||
message ServerConfig {
|
||||
AuthType auth_type = 1;
|
||||
map<string, string> accounts = 2;
|
||||
xray.common.net.IPOrDomain address = 3;
|
||||
bool udp_enabled = 4;
|
||||
uint32 timeout = 5 [deprecated = true];
|
||||
uint32 user_level = 6;
|
||||
}
|
||||
|
||||
// ClientConfig is the protobuf config for Socks client.
|
||||
message ClientConfig {
|
||||
// Sever is a list of Socks server addresses.
|
||||
repeated xray.common.protocol.ServerEndpoint server = 1;
|
||||
}
|
9
proxy/socks/errors.generated.go
Normal file
9
proxy/socks/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package socks
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
490
proxy/socks/protocol.go
Normal file
490
proxy/socks/protocol.go
Normal file
|
@ -0,0 +1,490 @@
|
|||
// +build !confonly
|
||||
|
||||
package socks
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/common/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
socks5Version = 0x05
|
||||
socks4Version = 0x04
|
||||
|
||||
cmdTCPConnect = 0x01
|
||||
cmdTCPBind = 0x02
|
||||
cmdUDPPort = 0x03
|
||||
cmdTorResolve = 0xF0
|
||||
cmdTorResolvePTR = 0xF1
|
||||
|
||||
socks4RequestGranted = 90
|
||||
socks4RequestRejected = 91
|
||||
|
||||
authNotRequired = 0x00
|
||||
// authGssAPI = 0x01
|
||||
authPassword = 0x02
|
||||
authNoMatchingMethod = 0xFF
|
||||
|
||||
statusSuccess = 0x00
|
||||
statusCmdNotSupport = 0x07
|
||||
)
|
||||
|
||||
var addrParser = protocol.NewAddressParser(
|
||||
protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),
|
||||
protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),
|
||||
protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),
|
||||
)
|
||||
|
||||
type ServerSession struct {
|
||||
config *ServerConfig
|
||||
port net.Port
|
||||
}
|
||||
|
||||
func (s *ServerSession) handshake4(cmd byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
|
||||
if s.config.AuthType == AuthType_PASSWORD {
|
||||
writeSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0))
|
||||
return nil, newError("socks 4 is not allowed when auth is required.")
|
||||
}
|
||||
|
||||
var port net.Port
|
||||
var address net.Address
|
||||
|
||||
{
|
||||
buffer := buf.StackNew()
|
||||
if _, err := buffer.ReadFullFrom(reader, 6); err != nil {
|
||||
buffer.Release()
|
||||
return nil, newError("insufficient header").Base(err)
|
||||
}
|
||||
port = net.PortFromBytes(buffer.BytesRange(0, 2))
|
||||
address = net.IPAddress(buffer.BytesRange(2, 6))
|
||||
buffer.Release()
|
||||
}
|
||||
|
||||
if _, err := ReadUntilNull(reader); /* user id */ err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if address.IP()[0] == 0x00 {
|
||||
domain, err := ReadUntilNull(reader)
|
||||
if err != nil {
|
||||
return nil, newError("failed to read domain for socks 4a").Base(err)
|
||||
}
|
||||
address = net.DomainAddress(domain)
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case cmdTCPConnect:
|
||||
request := &protocol.RequestHeader{
|
||||
Command: protocol.RequestCommandTCP,
|
||||
Address: address,
|
||||
Port: port,
|
||||
Version: socks4Version,
|
||||
}
|
||||
if err := writeSocks4Response(writer, socks4RequestGranted, net.AnyIP, net.Port(0)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return request, nil
|
||||
default:
|
||||
writeSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0))
|
||||
return nil, newError("unsupported command: ", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerSession) auth5(nMethod byte, reader io.Reader, writer io.Writer) (username string, err error) {
|
||||
buffer := buf.StackNew()
|
||||
defer buffer.Release()
|
||||
|
||||
if _, err = buffer.ReadFullFrom(reader, int32(nMethod)); err != nil {
|
||||
return "", newError("failed to read auth methods").Base(err)
|
||||
}
|
||||
|
||||
var expectedAuth byte = authNotRequired
|
||||
if s.config.AuthType == AuthType_PASSWORD {
|
||||
expectedAuth = authPassword
|
||||
}
|
||||
|
||||
if !hasAuthMethod(expectedAuth, buffer.BytesRange(0, int32(nMethod))) {
|
||||
writeSocks5AuthenticationResponse(writer, socks5Version, authNoMatchingMethod)
|
||||
return "", newError("no matching auth method")
|
||||
}
|
||||
|
||||
if err := writeSocks5AuthenticationResponse(writer, socks5Version, expectedAuth); err != nil {
|
||||
return "", newError("failed to write auth response").Base(err)
|
||||
}
|
||||
|
||||
if expectedAuth == authPassword {
|
||||
username, password, err := ReadUsernamePassword(reader)
|
||||
if err != nil {
|
||||
return "", newError("failed to read username and password for authentication").Base(err)
|
||||
}
|
||||
|
||||
if !s.config.HasAccount(username, password) {
|
||||
writeSocks5AuthenticationResponse(writer, 0x01, 0xFF)
|
||||
return "", newError("invalid username or password")
|
||||
}
|
||||
|
||||
if err := writeSocks5AuthenticationResponse(writer, 0x01, 0x00); err != nil {
|
||||
return "", newError("failed to write auth response").Base(err)
|
||||
}
|
||||
return username, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
|
||||
var (
|
||||
username string
|
||||
err error
|
||||
)
|
||||
if username, err = s.auth5(nMethod, reader, writer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cmd byte
|
||||
{
|
||||
buffer := buf.StackNew()
|
||||
if _, err := buffer.ReadFullFrom(reader, 3); err != nil {
|
||||
buffer.Release()
|
||||
return nil, newError("failed to read request").Base(err)
|
||||
}
|
||||
cmd = buffer.Byte(1)
|
||||
buffer.Release()
|
||||
}
|
||||
|
||||
request := new(protocol.RequestHeader)
|
||||
if username != "" {
|
||||
request.User = &protocol.MemoryUser{Email: username}
|
||||
}
|
||||
switch cmd {
|
||||
case cmdTCPConnect, cmdTorResolve, cmdTorResolvePTR:
|
||||
// We don't have a solution for Tor case now. Simply treat it as connect command.
|
||||
request.Command = protocol.RequestCommandTCP
|
||||
case cmdUDPPort:
|
||||
if !s.config.UdpEnabled {
|
||||
writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))
|
||||
return nil, newError("UDP is not enabled.")
|
||||
}
|
||||
request.Command = protocol.RequestCommandUDP
|
||||
case cmdTCPBind:
|
||||
writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))
|
||||
return nil, newError("TCP bind is not supported.")
|
||||
default:
|
||||
writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))
|
||||
return nil, newError("unknown command ", cmd)
|
||||
}
|
||||
|
||||
request.Version = socks5Version
|
||||
|
||||
addr, port, err := addrParser.ReadAddressPort(nil, reader)
|
||||
if err != nil {
|
||||
return nil, newError("failed to read address").Base(err)
|
||||
}
|
||||
request.Address = addr
|
||||
request.Port = port
|
||||
|
||||
responseAddress := net.AnyIP
|
||||
responsePort := net.Port(1717)
|
||||
if request.Command == protocol.RequestCommandUDP {
|
||||
addr := s.config.Address.AsAddress()
|
||||
if addr == nil {
|
||||
addr = net.LocalHostIP
|
||||
}
|
||||
responseAddress = addr
|
||||
responsePort = s.port
|
||||
}
|
||||
if err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// Handshake performs a Socks4/4a/5 handshake.
|
||||
func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
|
||||
buffer := buf.StackNew()
|
||||
if _, err := buffer.ReadFullFrom(reader, 2); err != nil {
|
||||
buffer.Release()
|
||||
return nil, newError("insufficient header").Base(err)
|
||||
}
|
||||
|
||||
version := buffer.Byte(0)
|
||||
cmd := buffer.Byte(1)
|
||||
buffer.Release()
|
||||
|
||||
switch version {
|
||||
case socks4Version:
|
||||
return s.handshake4(cmd, reader, writer)
|
||||
case socks5Version:
|
||||
return s.handshake5(cmd, reader, writer)
|
||||
default:
|
||||
return nil, newError("unknown Socks version: ", version)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadUsernamePassword reads Socks 5 username/password message from the given reader.
|
||||
// +----+------+----------+------+----------+
|
||||
// |VER | ULEN | UNAME | PLEN | PASSWD |
|
||||
// +----+------+----------+------+----------+
|
||||
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
|
||||
// +----+------+----------+------+----------+
|
||||
func ReadUsernamePassword(reader io.Reader) (string, string, error) {
|
||||
buffer := buf.StackNew()
|
||||
defer buffer.Release()
|
||||
|
||||
if _, err := buffer.ReadFullFrom(reader, 2); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
nUsername := int32(buffer.Byte(1))
|
||||
|
||||
buffer.Clear()
|
||||
if _, err := buffer.ReadFullFrom(reader, nUsername); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
username := buffer.String()
|
||||
|
||||
buffer.Clear()
|
||||
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
nPassword := int32(buffer.Byte(0))
|
||||
|
||||
buffer.Clear()
|
||||
if _, err := buffer.ReadFullFrom(reader, nPassword); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
password := buffer.String()
|
||||
return username, password, nil
|
||||
}
|
||||
|
||||
// ReadUntilNull reads content from given reader, until a null (0x00) byte.
|
||||
func ReadUntilNull(reader io.Reader) (string, error) {
|
||||
b := buf.StackNew()
|
||||
defer b.Release()
|
||||
|
||||
for {
|
||||
_, err := b.ReadFullFrom(reader, 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b.Byte(b.Len()-1) == 0x00 {
|
||||
b.Resize(0, b.Len()-1)
|
||||
return b.String(), nil
|
||||
}
|
||||
if b.IsFull() {
|
||||
return "", newError("buffer overrun")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasAuthMethod(expectedAuth byte, authCandidates []byte) bool {
|
||||
for _, a := range authCandidates {
|
||||
if a == expectedAuth {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writeSocks5AuthenticationResponse(writer io.Writer, version byte, auth byte) error {
|
||||
return buf.WriteAllBytes(writer, []byte{version, auth})
|
||||
}
|
||||
|
||||
func writeSocks5Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error {
|
||||
buffer := buf.New()
|
||||
defer buffer.Release()
|
||||
|
||||
common.Must2(buffer.Write([]byte{socks5Version, errCode, 0x00 /* reserved */}))
|
||||
if err := addrParser.WriteAddressPort(buffer, address, port); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return buf.WriteAllBytes(writer, buffer.Bytes())
|
||||
}
|
||||
|
||||
func writeSocks4Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error {
|
||||
buffer := buf.StackNew()
|
||||
defer buffer.Release()
|
||||
|
||||
common.Must(buffer.WriteByte(0x00))
|
||||
common.Must(buffer.WriteByte(errCode))
|
||||
portBytes := buffer.Extend(2)
|
||||
binary.BigEndian.PutUint16(portBytes, port.Value())
|
||||
common.Must2(buffer.Write(address.IP()))
|
||||
return buf.WriteAllBytes(writer, buffer.Bytes())
|
||||
}
|
||||
|
||||
func DecodeUDPPacket(packet *buf.Buffer) (*protocol.RequestHeader, error) {
|
||||
if packet.Len() < 5 {
|
||||
return nil, newError("insufficient length of packet.")
|
||||
}
|
||||
request := &protocol.RequestHeader{
|
||||
Version: socks5Version,
|
||||
Command: protocol.RequestCommandUDP,
|
||||
}
|
||||
|
||||
// packet[0] and packet[1] are reserved
|
||||
if packet.Byte(2) != 0 /* fragments */ {
|
||||
return nil, newError("discarding fragmented payload.")
|
||||
}
|
||||
|
||||
packet.Advance(3)
|
||||
|
||||
addr, port, err := addrParser.ReadAddressPort(nil, packet)
|
||||
if err != nil {
|
||||
return nil, newError("failed to read UDP header").Base(err)
|
||||
}
|
||||
request.Address = addr
|
||||
request.Port = port
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func EncodeUDPPacket(request *protocol.RequestHeader, data []byte) (*buf.Buffer, error) {
|
||||
b := buf.New()
|
||||
common.Must2(b.Write([]byte{0, 0, 0 /* Fragment */}))
|
||||
if err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
common.Must2(b.Write(data))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type UDPReader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func NewUDPReader(reader io.Reader) *UDPReader {
|
||||
return &UDPReader{reader: reader}
|
||||
}
|
||||
|
||||
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
b := buf.New()
|
||||
if _, err := b.ReadFrom(r.reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := DecodeUDPPacket(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.MultiBuffer{b}, nil
|
||||
}
|
||||
|
||||
type UDPWriter struct {
|
||||
request *protocol.RequestHeader
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewUDPWriter(request *protocol.RequestHeader, writer io.Writer) *UDPWriter {
|
||||
return &UDPWriter{
|
||||
request: request,
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (w *UDPWriter) Write(b []byte) (int, error) {
|
||||
eb, err := EncodeUDPPacket(w.request, b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer eb.Release()
|
||||
if _, err := w.writer.Write(eb.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
|
||||
authByte := byte(authNotRequired)
|
||||
if request.User != nil {
|
||||
authByte = byte(authPassword)
|
||||
}
|
||||
|
||||
b := buf.New()
|
||||
defer b.Release()
|
||||
|
||||
common.Must2(b.Write([]byte{socks5Version, 0x01, authByte}))
|
||||
if authByte == authPassword {
|
||||
account := request.User.Account.(*Account)
|
||||
|
||||
common.Must(b.WriteByte(0x01))
|
||||
common.Must(b.WriteByte(byte(len(account.Username))))
|
||||
common.Must2(b.WriteString(account.Username))
|
||||
common.Must(b.WriteByte(byte(len(account.Password))))
|
||||
common.Must2(b.WriteString(account.Password))
|
||||
}
|
||||
|
||||
if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Clear()
|
||||
if _, err := b.ReadFullFrom(reader, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b.Byte(0) != socks5Version {
|
||||
return nil, newError("unexpected server version: ", b.Byte(0)).AtWarning()
|
||||
}
|
||||
if b.Byte(1) != authByte {
|
||||
return nil, newError("auth method not supported.").AtWarning()
|
||||
}
|
||||
|
||||
if authByte == authPassword {
|
||||
b.Clear()
|
||||
if _, err := b.ReadFullFrom(reader, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.Byte(1) != 0x00 {
|
||||
return nil, newError("server rejects account: ", b.Byte(1))
|
||||
}
|
||||
}
|
||||
|
||||
b.Clear()
|
||||
|
||||
command := byte(cmdTCPConnect)
|
||||
if request.Command == protocol.RequestCommandUDP {
|
||||
command = byte(cmdUDPPort)
|
||||
}
|
||||
common.Must2(b.Write([]byte{socks5Version, command, 0x00 /* reserved */}))
|
||||
if err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Clear()
|
||||
if _, err := b.ReadFullFrom(reader, 3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := b.Byte(1)
|
||||
if resp != 0x00 {
|
||||
return nil, newError("server rejects request: ", resp)
|
||||
}
|
||||
|
||||
b.Clear()
|
||||
|
||||
address, port, err := addrParser.ReadAddressPort(b, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if request.Command == protocol.RequestCommandUDP {
|
||||
udpRequest := &protocol.RequestHeader{
|
||||
Version: socks5Version,
|
||||
Command: protocol.RequestCommandUDP,
|
||||
Address: address,
|
||||
Port: port,
|
||||
}
|
||||
return udpRequest, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
124
proxy/socks/protocol_test.go
Normal file
124
proxy/socks/protocol_test.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package socks_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/common/protocol"
|
||||
. "github.com/xtls/xray-core/v1/proxy/socks"
|
||||
)
|
||||
|
||||
func TestUDPEncoding(t *testing.T) {
|
||||
b := buf.New()
|
||||
|
||||
request := &protocol.RequestHeader{
|
||||
Address: net.IPAddress([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}),
|
||||
Port: 1024,
|
||||
}
|
||||
writer := &buf.SequentialWriter{Writer: NewUDPWriter(request, b)}
|
||||
|
||||
content := []byte{'a'}
|
||||
payload := buf.New()
|
||||
payload.Write(content)
|
||||
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{payload}))
|
||||
|
||||
reader := NewUDPReader(b)
|
||||
|
||||
decodedPayload, err := reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(decodedPayload[0].Bytes(), content); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUsernamePassword(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input []byte
|
||||
Username string
|
||||
Password string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
Input: []byte{0x05, 0x01, 'a', 0x02, 'b', 'c'},
|
||||
Username: "a",
|
||||
Password: "bc",
|
||||
},
|
||||
{
|
||||
Input: []byte{0x05, 0x18, 'a', 0x02, 'b', 'c'},
|
||||
Error: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
reader := bytes.NewReader(testCase.Input)
|
||||
username, password, err := ReadUsernamePassword(reader)
|
||||
if testCase.Error {
|
||||
if err == nil {
|
||||
t.Error("for input: ", testCase.Input, " expect error, but actually nil")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Error("for input: ", testCase.Input, " expect no error, but actually ", err.Error())
|
||||
}
|
||||
if testCase.Username != username {
|
||||
t.Error("for input: ", testCase.Input, " expect username ", testCase.Username, " but actually ", username)
|
||||
}
|
||||
if testCase.Password != password {
|
||||
t.Error("for input: ", testCase.Input, " expect passowrd ", testCase.Password, " but actually ", password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUntilNull(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input []byte
|
||||
Output string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
Input: []byte{'a', 'b', 0x00},
|
||||
Output: "ab",
|
||||
},
|
||||
{
|
||||
Input: []byte{'a'},
|
||||
Error: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
reader := bytes.NewReader(testCase.Input)
|
||||
value, err := ReadUntilNull(reader)
|
||||
if testCase.Error {
|
||||
if err == nil {
|
||||
t.Error("for input: ", testCase.Input, " expect error, but actually nil")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Error("for input: ", testCase.Input, " expect no error, but actually ", err.Error())
|
||||
}
|
||||
if testCase.Output != value {
|
||||
t.Error("for input: ", testCase.Input, " expect output ", testCase.Output, " but actually ", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReadUsernamePassword(b *testing.B) {
|
||||
input := []byte{0x05, 0x01, 'a', 0x02, 'b', 'c'}
|
||||
buffer := buf.New()
|
||||
buffer.Write(input)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := ReadUsernamePassword(buffer)
|
||||
common.Must(err)
|
||||
buffer.Clear()
|
||||
buffer.Extend(int32(len(input)))
|
||||
}
|
||||
}
|
253
proxy/socks/server.go
Normal file
253
proxy/socks/server.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
// +build !confonly
|
||||
|
||||
package socks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/log"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/common/protocol"
|
||||
udp_proto "github.com/xtls/xray-core/v1/common/protocol/udp"
|
||||
"github.com/xtls/xray-core/v1/common/session"
|
||||
"github.com/xtls/xray-core/v1/common/signal"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/features"
|
||||
"github.com/xtls/xray-core/v1/features/policy"
|
||||
"github.com/xtls/xray-core/v1/features/routing"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/udp"
|
||||
)
|
||||
|
||||
// Server is a SOCKS 5 proxy server
|
||||
type Server struct {
|
||||
config *ServerConfig
|
||||
policyManager policy.Manager
|
||||
}
|
||||
|
||||
// NewServer creates a new Server object.
|
||||
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
s := &Server{
|
||||
config: config,
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) policy() policy.Session {
|
||||
config := s.config
|
||||
p := s.policyManager.ForLevel(config.UserLevel)
|
||||
if config.Timeout > 0 {
|
||||
features.PrintDeprecatedFeatureWarning("Socks timeout")
|
||||
}
|
||||
if config.Timeout > 0 && config.UserLevel == 0 {
|
||||
p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Network implements proxy.Inbound.
|
||||
func (s *Server) Network() []net.Network {
|
||||
list := []net.Network{net.Network_TCP}
|
||||
if s.config.UdpEnabled {
|
||||
list = append(list, net.Network_UDP)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Process implements proxy.Inbound.
|
||||
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||
inbound.User = &protocol.MemoryUser{
|
||||
Level: s.config.UserLevel,
|
||||
}
|
||||
}
|
||||
|
||||
switch network {
|
||||
case net.Network_TCP:
|
||||
return s.processTCP(ctx, conn, dispatcher)
|
||||
case net.Network_UDP:
|
||||
return s.handleUDPPayload(ctx, conn, dispatcher)
|
||||
default:
|
||||
return newError("unknown network: ", network)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error {
|
||||
plcy := s.policy()
|
||||
if err := conn.SetReadDeadline(time.Now().Add(plcy.Timeouts.Handshake)); err != nil {
|
||||
newError("failed to set deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
if inbound == nil || !inbound.Gateway.IsValid() {
|
||||
return newError("inbound gateway not specified")
|
||||
}
|
||||
|
||||
svrSession := &ServerSession{
|
||||
config: s.config,
|
||||
port: inbound.Gateway.Port,
|
||||
}
|
||||
|
||||
reader := &buf.BufferedReader{Reader: buf.NewReader(conn)}
|
||||
request, err := svrSession.Handshake(reader, conn)
|
||||
if err != nil {
|
||||
if inbound != nil && inbound.Source.IsValid() {
|
||||
log.Record(&log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
To: "",
|
||||
Status: log.AccessRejected,
|
||||
Reason: err,
|
||||
})
|
||||
}
|
||||
return newError("failed to read request").Base(err)
|
||||
}
|
||||
if request.User != nil {
|
||||
inbound.User.Email = request.User.Email
|
||||
}
|
||||
|
||||
if err := conn.SetReadDeadline(time.Time{}); err != nil {
|
||||
newError("failed to clear deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
|
||||
if request.Command == protocol.RequestCommandTCP {
|
||||
dest := request.Destination()
|
||||
newError("TCP Connect request to ", dest).WriteToLog(session.ExportIDToError(ctx))
|
||||
if inbound != nil && inbound.Source.IsValid() {
|
||||
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
To: dest,
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
})
|
||||
}
|
||||
|
||||
return s.transport(ctx, reader, conn, dest, dispatcher)
|
||||
}
|
||||
|
||||
if request.Command == protocol.RequestCommandUDP {
|
||||
return s.handleUDP(conn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Server) handleUDP(c io.Reader) error {
|
||||
// The TCP connection closes after this method returns. We need to wait until
|
||||
// the client closes it.
|
||||
return common.Error2(io.Copy(buf.DiscardBytes, c))
|
||||
}
|
||||
|
||||
func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
timer := signal.CancelAfterInactivity(ctx, cancel, s.policy().Timeouts.ConnectionIdle)
|
||||
|
||||
plcy := s.policy()
|
||||
ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer)
|
||||
link, err := dispatcher.Dispatch(ctx, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestDone := func() error {
|
||||
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
|
||||
if err := buf.Copy(buf.NewReader(reader), link.Writer, buf.UpdateActivity(timer)); err != nil {
|
||||
return newError("failed to transport all TCP request").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
responseDone := func() error {
|
||||
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
|
||||
|
||||
v2writer := buf.NewWriter(writer)
|
||||
if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil {
|
||||
return newError("failed to transport all TCP response").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer))
|
||||
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
|
||||
common.Interrupt(link.Reader)
|
||||
common.Interrupt(link.Writer)
|
||||
return newError("connection ends").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error {
|
||||
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
|
||||
payload := packet.Payload
|
||||
newError("writing back UDP response with ", payload.Len(), " bytes").AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||
|
||||
request := protocol.RequestHeaderFromContext(ctx)
|
||||
if request == nil {
|
||||
return
|
||||
}
|
||||
udpMessage, err := EncodeUDPPacket(request, payload.Bytes())
|
||||
payload.Release()
|
||||
|
||||
defer udpMessage.Release()
|
||||
if err != nil {
|
||||
newError("failed to write UDP response").AtWarning().Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
|
||||
conn.Write(udpMessage.Bytes())
|
||||
})
|
||||
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() {
|
||||
newError("client UDP connection from ", inbound.Source).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
|
||||
reader := buf.NewPacketReader(conn)
|
||||
for {
|
||||
mpayload, err := reader.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, payload := range mpayload {
|
||||
request, err := DecodeUDPPacket(payload)
|
||||
|
||||
if err != nil {
|
||||
newError("failed to parse UDP request").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
payload.Release()
|
||||
continue
|
||||
}
|
||||
|
||||
if payload.IsEmpty() {
|
||||
payload.Release()
|
||||
continue
|
||||
}
|
||||
currentPacketCtx := ctx
|
||||
newError("send packet to ", request.Destination(), " with ", payload.Len(), " bytes").AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() {
|
||||
currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
To: request.Destination(),
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
})
|
||||
}
|
||||
|
||||
currentPacketCtx = protocol.ContextWithRequestHeader(currentPacketCtx, request)
|
||||
udpServer.Dispatch(currentPacketCtx, request.Destination(), payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return NewServer(ctx, config.(*ServerConfig))
|
||||
}))
|
||||
}
|
4
proxy/socks/socks.go
Normal file
4
proxy/socks/socks.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Package socks provides implements of Socks protocol 4, 4a and 5.
|
||||
package socks
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
Loading…
Add table
Add a link
Reference in a new issue