This commit is contained in:
RPRX 2020-11-25 19:01:53 +08:00
parent 47d23e9972
commit c7f7c08ead
711 changed files with 82154 additions and 2 deletions

View file

@ -0,0 +1,48 @@
// +build !confonly
// Package blackhole is an outbound handler that blocks all connections.
package blackhole
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
import (
"context"
"time"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
)
// Handler is an outbound connection that silently swallow the entire payload.
type Handler struct {
response ResponseConfig
}
// New creates a new blackhole handler.
func New(ctx context.Context, config *Config) (*Handler, error) {
response, err := config.GetInternalResponse()
if err != nil {
return nil, err
}
return &Handler{
response: response,
}, nil
}
// Process implements OutboundHandler.Dispatch().
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
nBytes := h.response.WriteTo(link.Writer)
if nBytes > 0 {
// Sleep a little here to make sure the response is sent to client.
time.Sleep(time.Second)
}
common.Interrupt(link.Writer)
return nil
}
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config))
}))
}

View file

@ -0,0 +1,40 @@
package blackhole_test
import (
"context"
"testing"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/serial"
"github.com/xtls/xray-core/v1/proxy/blackhole"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/pipe"
)
func TestBlackholeHTTPResponse(t *testing.T) {
handler, err := blackhole.New(context.Background(), &blackhole.Config{
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
})
common.Must(err)
reader, writer := pipe.New(pipe.WithoutSizeLimit())
var mb buf.MultiBuffer
var rerr error
go func() {
b, e := reader.ReadMultiBuffer()
mb = b
rerr = e
}()
link := transport.Link{
Reader: reader,
Writer: writer,
}
common.Must(handler.Process(context.Background(), &link, nil))
common.Must(rerr)
if mb.IsEmpty() {
t.Error("expect http response, but nothing")
}
}

47
proxy/blackhole/config.go Normal file
View file

@ -0,0 +1,47 @@
package blackhole
import (
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
)
const (
http403response = `HTTP/1.1 403 Forbidden
Connection: close
Cache-Control: max-age=3600, public
Content-Length: 0
`
)
// ResponseConfig is the configuration for blackhole responses.
type ResponseConfig interface {
// WriteTo writes predefined response to the give buffer.
WriteTo(buf.Writer) int32
}
// WriteTo implements ResponseConfig.WriteTo().
func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }
// WriteTo implements ResponseConfig.WriteTo().
func (*HTTPResponse) WriteTo(writer buf.Writer) int32 {
b := buf.New()
common.Must2(b.WriteString(http403response))
n := b.Len()
writer.WriteMultiBuffer(buf.MultiBuffer{b})
return n
}
// GetInternalResponse converts response settings from proto to internal data structure.
func (c *Config) GetInternalResponse() (ResponseConfig, error) {
if c.GetResponse() == nil {
return new(NoneResponse), nil
}
config, err := c.GetResponse().GetInstance()
if err != nil {
return nil, err
}
return config.(ResponseConfig), nil
}

View file

@ -0,0 +1,265 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/blackhole/config.proto
package blackhole
import (
proto "github.com/golang/protobuf/proto"
serial "github.com/xtls/xray-core/v1/common/serial"
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
type NoneResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *NoneResponse) Reset() {
*x = NoneResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NoneResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NoneResponse) ProtoMessage() {}
func (x *NoneResponse) ProtoReflect() protoreflect.Message {
mi := &file_proxy_blackhole_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 NoneResponse.ProtoReflect.Descriptor instead.
func (*NoneResponse) Descriptor() ([]byte, []int) {
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
}
type HTTPResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *HTTPResponse) Reset() {
*x = HTTPResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HTTPResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HTTPResponse) ProtoMessage() {}
func (x *HTTPResponse) ProtoReflect() protoreflect.Message {
mi := &file_proxy_blackhole_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 HTTPResponse.ProtoReflect.Descriptor instead.
func (*HTTPResponse) Descriptor() ([]byte, []int) {
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Response *serial.TypedMessage `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_blackhole_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 Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2}
}
func (x *Config) GetResponse() *serial.TypedMessage {
if x != nil {
return x.Response
}
return nil
}
var File_proxy_blackhole_config_proto protoreflect.FileDescriptor
var file_proxy_blackhole_config_proto_rawDesc = []byte{
0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c,
0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x62, 0x6c, 0x61, 0x63, 0x6b,
0x68, 0x6f, 0x6c, 0x65, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0e, 0x0a, 0x0c, 0x4e, 0x6f, 0x6e, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x54, 0x54, 0x50, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x12, 0x3c, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42,
0x61, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78,
0x79, 0x2e, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0x50, 0x01, 0x5a, 0x2c, 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, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0xaa, 0x02, 0x14, 0x58, 0x72,
0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f,
0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_blackhole_config_proto_rawDescOnce sync.Once
file_proxy_blackhole_config_proto_rawDescData = file_proxy_blackhole_config_proto_rawDesc
)
func file_proxy_blackhole_config_proto_rawDescGZIP() []byte {
file_proxy_blackhole_config_proto_rawDescOnce.Do(func() {
file_proxy_blackhole_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_blackhole_config_proto_rawDescData)
})
return file_proxy_blackhole_config_proto_rawDescData
}
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_proxy_blackhole_config_proto_goTypes = []interface{}{
(*NoneResponse)(nil), // 0: xray.proxy.blackhole.NoneResponse
(*HTTPResponse)(nil), // 1: xray.proxy.blackhole.HTTPResponse
(*Config)(nil), // 2: xray.proxy.blackhole.Config
(*serial.TypedMessage)(nil), // 3: xray.common.serial.TypedMessage
}
var file_proxy_blackhole_config_proto_depIdxs = []int32{
3, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.common.serial.TypedMessage
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_blackhole_config_proto_init() }
func file_proxy_blackhole_config_proto_init() {
if File_proxy_blackhole_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_blackhole_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NoneResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_blackhole_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HTTPResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_blackhole_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); 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_blackhole_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_blackhole_config_proto_goTypes,
DependencyIndexes: file_proxy_blackhole_config_proto_depIdxs,
MessageInfos: file_proxy_blackhole_config_proto_msgTypes,
}.Build()
File_proxy_blackhole_config_proto = out.File
file_proxy_blackhole_config_proto_rawDesc = nil
file_proxy_blackhole_config_proto_goTypes = nil
file_proxy_blackhole_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,17 @@
syntax = "proto3";
package xray.proxy.blackhole;
option csharp_namespace = "Xray.Proxy.Blackhole";
option go_package = "github.com/xtls/xray-core/v1/proxy/blackhole";
option java_package = "com.xray.proxy.blackhole";
option java_multiple_files = true;
import "common/serial/typed_message.proto";
message NoneResponse {}
message HTTPResponse {}
message Config {
xray.common.serial.TypedMessage response = 1;
}

View file

@ -0,0 +1,26 @@
package blackhole_test
import (
"bufio"
"net/http"
"testing"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
. "github.com/xtls/xray-core/v1/proxy/blackhole"
)
func TestHTTPResponse(t *testing.T) {
buffer := buf.New()
httpResponse := new(HTTPResponse)
httpResponse.WriteTo(buf.NewWriter(buffer))
reader := bufio.NewReader(buffer)
response, err := http.ReadResponse(reader, nil)
common.Must(err)
if response.StatusCode != 403 {
t.Error("expected status code 403, but got ", response.StatusCode)
}
}

View file

@ -0,0 +1,9 @@
package blackhole
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

160
proxy/dns/config.pb.go Normal file
View file

@ -0,0 +1,160 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/dns/config.proto
package dns
import (
proto "github.com/golang/protobuf/proto"
net "github.com/xtls/xray-core/v1/common/net"
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
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Server is the DNS server address. If specified, this address overrides the
// original one.
Server *net.Endpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_dns_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_dns_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 Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_dns_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetServer() *net.Endpoint {
if x != nil {
return x.Server
}
return nil
}
var File_proxy_dns_config_proto protoreflect.FileDescriptor
var file_proxy_dns_config_proto_rawDesc = []byte{
0x0a, 0x16, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3b, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x12, 0x31, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e,
0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x42, 0x4f, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x26, 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,
0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0e, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79,
0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_dns_config_proto_rawDescOnce sync.Once
file_proxy_dns_config_proto_rawDescData = file_proxy_dns_config_proto_rawDesc
)
func file_proxy_dns_config_proto_rawDescGZIP() []byte {
file_proxy_dns_config_proto_rawDescOnce.Do(func() {
file_proxy_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_dns_config_proto_rawDescData)
})
return file_proxy_dns_config_proto_rawDescData
}
var file_proxy_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_dns_config_proto_goTypes = []interface{}{
(*Config)(nil), // 0: xray.proxy.dns.Config
(*net.Endpoint)(nil), // 1: xray.common.net.Endpoint
}
var file_proxy_dns_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.dns.Config.server:type_name -> xray.common.net.Endpoint
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_dns_config_proto_init() }
func file_proxy_dns_config_proto_init() {
if File_proxy_dns_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_dns_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); 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_dns_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_dns_config_proto_goTypes,
DependencyIndexes: file_proxy_dns_config_proto_depIdxs,
MessageInfos: file_proxy_dns_config_proto_msgTypes,
}.Build()
File_proxy_dns_config_proto = out.File
file_proxy_dns_config_proto_rawDesc = nil
file_proxy_dns_config_proto_goTypes = nil
file_proxy_dns_config_proto_depIdxs = nil
}

15
proxy/dns/config.proto Normal file
View file

@ -0,0 +1,15 @@
syntax = "proto3";
package xray.proxy.dns;
option csharp_namespace = "Xray.Proxy.Dns";
option go_package = "github.com/xtls/xray-core/v1/proxy/dns";
option java_package = "com.xray.proxy.dns";
option java_multiple_files = true;
import "common/net/destination.proto";
message Config {
// Server is the DNS server address. If specified, this address overrides the
// original one.
xray.common.net.Endpoint server = 1;
}

330
proxy/dns/dns.go Normal file
View file

@ -0,0 +1,330 @@
// +build !confonly
package dns
import (
"context"
"io"
"sync"
"golang.org/x/net/dns/dnsmessage"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/net"
dns_proto "github.com/xtls/xray-core/v1/common/protocol/dns"
"github.com/xtls/xray-core/v1/common/session"
"github.com/xtls/xray-core/v1/common/task"
"github.com/xtls/xray-core/v1/core"
"github.com/xtls/xray-core/v1/features/dns"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler)
if err := core.RequireFeatures(ctx, func(dnsClient dns.Client) error {
return h.Init(config.(*Config), dnsClient)
}); err != nil {
return nil, err
}
return h, nil
}))
}
type ownLinkVerifier interface {
IsOwnLink(ctx context.Context) bool
}
type Handler struct {
ipv4Lookup dns.IPv4Lookup
ipv6Lookup dns.IPv6Lookup
ownLinkVerifier ownLinkVerifier
server net.Destination
}
func (h *Handler) Init(config *Config, dnsClient dns.Client) error {
ipv4lookup, ok := dnsClient.(dns.IPv4Lookup)
if !ok {
return newError("dns.Client doesn't implement IPv4Lookup")
}
h.ipv4Lookup = ipv4lookup
ipv6lookup, ok := dnsClient.(dns.IPv6Lookup)
if !ok {
return newError("dns.Client doesn't implement IPv6Lookup")
}
h.ipv6Lookup = ipv6lookup
if v, ok := dnsClient.(ownLinkVerifier); ok {
h.ownLinkVerifier = v
}
if config.Server != nil {
h.server = config.Server.AsDestination()
}
return nil
}
func (h *Handler) isOwnLink(ctx context.Context) bool {
return h.ownLinkVerifier != nil && h.ownLinkVerifier.IsOwnLink(ctx)
}
func parseIPQuery(b []byte) (r bool, domain string, id uint16, qType dnsmessage.Type) {
var parser dnsmessage.Parser
header, err := parser.Start(b)
if err != nil {
newError("parser start").Base(err).WriteToLog()
return
}
id = header.ID
q, err := parser.Question()
if err != nil {
newError("question").Base(err).WriteToLog()
return
}
qType = q.Type
if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA {
return
}
domain = q.Name.String()
r = true
return
}
// Process implements proxy.Outbound.
func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.Dialer) error {
outbound := session.OutboundFromContext(ctx)
if outbound == nil || !outbound.Target.IsValid() {
return newError("invalid outbound")
}
srcNetwork := outbound.Target.Network
dest := outbound.Target
if h.server.Network != net.Network_Unknown {
dest.Network = h.server.Network
}
if h.server.Address != nil {
dest.Address = h.server.Address
}
if h.server.Port != 0 {
dest.Port = h.server.Port
}
newError("handling DNS traffic to ", dest).WriteToLog(session.ExportIDToError(ctx))
conn := &outboundConn{
dialer: func() (internet.Connection, error) {
return d.Dial(ctx, dest)
},
connReady: make(chan struct{}, 1),
}
var reader dns_proto.MessageReader
var writer dns_proto.MessageWriter
if srcNetwork == net.Network_TCP {
reader = dns_proto.NewTCPReader(link.Reader)
writer = &dns_proto.TCPWriter{
Writer: link.Writer,
}
} else {
reader = &dns_proto.UDPReader{
Reader: link.Reader,
}
writer = &dns_proto.UDPWriter{
Writer: link.Writer,
}
}
var connReader dns_proto.MessageReader
var connWriter dns_proto.MessageWriter
if dest.Network == net.Network_TCP {
connReader = dns_proto.NewTCPReader(buf.NewReader(conn))
connWriter = &dns_proto.TCPWriter{
Writer: buf.NewWriter(conn),
}
} else {
connReader = &dns_proto.UDPReader{
Reader: buf.NewPacketReader(conn),
}
connWriter = &dns_proto.UDPWriter{
Writer: buf.NewWriter(conn),
}
}
request := func() error {
defer conn.Close()
for {
b, err := reader.ReadMessage()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if !h.isOwnLink(ctx) {
isIPQuery, domain, id, qType := parseIPQuery(b.Bytes())
if isIPQuery {
go h.handleIPQuery(id, qType, domain, writer)
continue
}
}
if err := connWriter.WriteMessage(b); err != nil {
return err
}
}
}
response := func() error {
for {
b, err := connReader.ReadMessage()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if err := writer.WriteMessage(b); err != nil {
return err
}
}
}
if err := task.Run(ctx, request, response); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) {
var ips []net.IP
var err error
switch qType {
case dnsmessage.TypeA:
ips, err = h.ipv4Lookup.LookupIPv4(domain)
case dnsmessage.TypeAAAA:
ips, err = h.ipv6Lookup.LookupIPv6(domain)
}
rcode := dns.RCodeFromError(err)
if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse {
newError("ip query").Base(err).WriteToLog()
return
}
b := buf.New()
rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
ID: id,
RCode: dnsmessage.RCode(rcode),
RecursionAvailable: true,
RecursionDesired: true,
Response: true,
Authoritative: true,
})
builder.EnableCompression()
common.Must(builder.StartQuestions())
common.Must(builder.Question(dnsmessage.Question{
Name: dnsmessage.MustNewName(domain),
Class: dnsmessage.ClassINET,
Type: qType,
}))
common.Must(builder.StartAnswers())
rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: 600}
for _, ip := range ips {
if len(ip) == net.IPv4len {
var r dnsmessage.AResource
copy(r.A[:], ip)
common.Must(builder.AResource(rHeader, r))
} else {
var r dnsmessage.AAAAResource
copy(r.AAAA[:], ip)
common.Must(builder.AAAAResource(rHeader, r))
}
}
msgBytes, err := builder.Finish()
if err != nil {
newError("pack message").Base(err).WriteToLog()
b.Release()
return
}
b.Resize(0, int32(len(msgBytes)))
if err := writer.WriteMessage(b); err != nil {
newError("write IP answer").Base(err).WriteToLog()
}
}
type outboundConn struct {
access sync.Mutex
dialer func() (internet.Connection, error)
conn net.Conn
connReady chan struct{}
}
func (c *outboundConn) dial() error {
conn, err := c.dialer()
if err != nil {
return err
}
c.conn = conn
c.connReady <- struct{}{}
return nil
}
func (c *outboundConn) Write(b []byte) (int, error) {
c.access.Lock()
if c.conn == nil {
if err := c.dial(); err != nil {
c.access.Unlock()
newError("failed to dial outbound connection").Base(err).AtWarning().WriteToLog()
return len(b), nil
}
}
c.access.Unlock()
return c.conn.Write(b)
}
func (c *outboundConn) Read(b []byte) (int, error) {
var conn net.Conn
c.access.Lock()
conn = c.conn
c.access.Unlock()
if conn == nil {
_, open := <-c.connReady
if !open {
return 0, io.EOF
}
conn = c.conn
}
return conn.Read(b)
}
func (c *outboundConn) Close() error {
c.access.Lock()
close(c.connReady)
if c.conn != nil {
c.conn.Close()
}
c.access.Unlock()
return nil
}

370
proxy/dns/dns_test.go Normal file
View file

@ -0,0 +1,370 @@
package dns_test
import (
"strconv"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/miekg/dns"
"github.com/xtls/xray-core/v1/app/dispatcher"
dnsapp "github.com/xtls/xray-core/v1/app/dns"
"github.com/xtls/xray-core/v1/app/policy"
"github.com/xtls/xray-core/v1/app/proxyman"
_ "github.com/xtls/xray-core/v1/app/proxyman/inbound"
_ "github.com/xtls/xray-core/v1/app/proxyman/outbound"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/serial"
"github.com/xtls/xray-core/v1/core"
dns_proxy "github.com/xtls/xray-core/v1/proxy/dns"
"github.com/xtls/xray-core/v1/proxy/dokodemo"
"github.com/xtls/xray-core/v1/testing/servers/tcp"
"github.com/xtls/xray-core/v1/testing/servers/udp"
)
type staticHandler struct {
}
func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
ans := new(dns.Msg)
ans.Id = r.Id
var clientIP net.IP
opt := r.IsEdns0()
if opt != nil {
for _, o := range opt.Option {
if o.Option() == dns.EDNS0SUBNET {
subnet := o.(*dns.EDNS0_SUBNET)
clientIP = subnet.Address
}
}
}
for _, q := range r.Question {
switch {
case q.Name == "google.com." && q.Qtype == dns.TypeA:
if clientIP == nil {
rr, _ := dns.NewRR("google.com. IN A 8.8.8.8")
ans.Answer = append(ans.Answer, rr)
} else {
rr, _ := dns.NewRR("google.com. IN A 8.8.4.4")
ans.Answer = append(ans.Answer, rr)
}
case q.Name == "facebook.com." && q.Qtype == dns.TypeA:
rr, _ := dns.NewRR("facebook.com. IN A 9.9.9.9")
ans.Answer = append(ans.Answer, rr)
case q.Name == "ipv6.google.com." && q.Qtype == dns.TypeA:
rr, err := dns.NewRR("ipv6.google.com. IN A 8.8.8.7")
common.Must(err)
ans.Answer = append(ans.Answer, rr)
case q.Name == "ipv6.google.com." && q.Qtype == dns.TypeAAAA:
rr, err := dns.NewRR("ipv6.google.com. IN AAAA 2001:4860:4860::8888")
common.Must(err)
ans.Answer = append(ans.Answer, rr)
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA:
ans.MsgHdr.Rcode = dns.RcodeNameError
}
}
w.WriteMsg(ans)
}
func TestUDPDNSTunnel(t *testing.T) {
port := udp.PickPort()
dnsServer := dns.Server{
Addr: "127.0.0.1:" + port.String(),
Net: "udp",
Handler: &staticHandler{},
UDPSize: 1200,
}
defer dnsServer.Shutdown()
go dnsServer.ListenAndServe()
time.Sleep(time.Second)
serverPort := udp.PickPort()
config := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&dnsapp.Config{
NameServers: []*net.Endpoint{
{
Network: net.Network_UDP,
Address: &net.IPOrDomain{
Address: &net.IPOrDomain_Ip{
Ip: []byte{127, 0, 0, 1},
},
},
Port: uint32(port),
},
},
}),
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&policy.Config{}),
},
Inbound: []*core.InboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(port),
Networks: []net.Network{net.Network_UDP},
}),
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(serverPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}),
},
},
}
v, err := core.New(config)
common.Must(err)
common.Must(v.Start())
defer v.Close()
{
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}
c := new(dns.Client)
in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort)))
common.Must(err)
if len(in.Answer) != 1 {
t.Fatal("len(answer): ", len(in.Answer))
}
rr, ok := in.Answer[0].(*dns.A)
if !ok {
t.Fatal("not A record")
}
if r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != "" {
t.Error(r)
}
}
{
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{Name: "ipv4only.google.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}
c := new(dns.Client)
c.Timeout = 10 * time.Second
in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort)))
common.Must(err)
if len(in.Answer) != 0 {
t.Fatal("len(answer): ", len(in.Answer))
}
}
{
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{Name: "notexist.google.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}
c := new(dns.Client)
in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort)))
common.Must(err)
if in.Rcode != dns.RcodeNameError {
t.Error("expected NameError, but got ", in.Rcode)
}
}
}
func TestTCPDNSTunnel(t *testing.T) {
port := udp.PickPort()
dnsServer := dns.Server{
Addr: "127.0.0.1:" + port.String(),
Net: "udp",
Handler: &staticHandler{},
}
defer dnsServer.Shutdown()
go dnsServer.ListenAndServe()
time.Sleep(time.Second)
serverPort := tcp.PickPort()
config := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&dnsapp.Config{
NameServer: []*dnsapp.NameServer{
{
Address: &net.Endpoint{
Network: net.Network_UDP,
Address: &net.IPOrDomain{
Address: &net.IPOrDomain_Ip{
Ip: []byte{127, 0, 0, 1},
},
},
Port: uint32(port),
},
},
},
}),
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&policy.Config{}),
},
Inbound: []*core.InboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(port),
Networks: []net.Network{net.Network_TCP},
}),
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(serverPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}),
},
},
}
v, err := core.New(config)
common.Must(err)
common.Must(v.Start())
defer v.Close()
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}
c := &dns.Client{
Net: "tcp",
}
in, _, err := c.Exchange(m1, "127.0.0.1:"+serverPort.String())
common.Must(err)
if len(in.Answer) != 1 {
t.Fatal("len(answer): ", len(in.Answer))
}
rr, ok := in.Answer[0].(*dns.A)
if !ok {
t.Fatal("not A record")
}
if r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != "" {
t.Error(r)
}
}
func TestUDP2TCPDNSTunnel(t *testing.T) {
port := tcp.PickPort()
dnsServer := dns.Server{
Addr: "127.0.0.1:" + port.String(),
Net: "tcp",
Handler: &staticHandler{},
}
defer dnsServer.Shutdown()
go dnsServer.ListenAndServe()
time.Sleep(time.Second)
serverPort := tcp.PickPort()
config := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&dnsapp.Config{
NameServer: []*dnsapp.NameServer{
{
Address: &net.Endpoint{
Network: net.Network_UDP,
Address: &net.IPOrDomain{
Address: &net.IPOrDomain_Ip{
Ip: []byte{127, 0, 0, 1},
},
},
Port: uint32(port),
},
},
},
}),
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&policy.Config{}),
},
Inbound: []*core.InboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(port),
Networks: []net.Network{net.Network_TCP},
}),
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(serverPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{
Server: &net.Endpoint{
Network: net.Network_TCP,
},
}),
},
},
}
v, err := core.New(config)
common.Must(err)
common.Must(v.Start())
defer v.Close()
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}
c := &dns.Client{
Net: "tcp",
}
in, _, err := c.Exchange(m1, "127.0.0.1:"+serverPort.String())
common.Must(err)
if len(in.Answer) != 1 {
t.Fatal("len(answer): ", len(in.Answer))
}
rr, ok := in.Answer[0].(*dns.A)
if !ok {
t.Fatal("not A record")
}
if r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != "" {
t.Error(r)
}
}

View file

@ -0,0 +1,9 @@
package dns
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

14
proxy/dokodemo/config.go Normal file
View file

@ -0,0 +1,14 @@
package dokodemo
import (
"github.com/xtls/xray-core/v1/common/net"
)
// GetPredefinedAddress returns the defined address from proto config. Null if address is not valid.
func (v *Config) GetPredefinedAddress() net.Address {
addr := v.Address.AsAddress()
if addr == nil {
return nil
}
return addr
}

237
proxy/dokodemo/config.pb.go Normal file
View file

@ -0,0 +1,237 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/dokodemo/config.proto
package dokodemo
import (
proto "github.com/golang/protobuf/proto"
net "github.com/xtls/xray-core/v1/common/net"
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
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
// List of networks that the Dokodemo accepts.
// Deprecated. Use networks.
//
// Deprecated: Do not use.
NetworkList *net.NetworkList `protobuf:"bytes,3,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"`
// List of networks that the Dokodemo accepts.
Networks []net.Network `protobuf:"varint,7,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
// Deprecated: Do not use.
Timeout uint32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"`
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"`
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_dokodemo_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_dokodemo_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 Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetAddress() *net.IPOrDomain {
if x != nil {
return x.Address
}
return nil
}
func (x *Config) GetPort() uint32 {
if x != nil {
return x.Port
}
return 0
}
// Deprecated: Do not use.
func (x *Config) GetNetworkList() *net.NetworkList {
if x != nil {
return x.NetworkList
}
return nil
}
func (x *Config) GetNetworks() []net.Network {
if x != nil {
return x.Networks
}
return nil
}
// Deprecated: Do not use.
func (x *Config) GetTimeout() uint32 {
if x != nil {
return x.Timeout
}
return 0
}
func (x *Config) GetFollowRedirect() bool {
if x != nil {
return x.FollowRedirect
}
return false
}
func (x *Config) GetUserLevel() uint32 {
if x != nil {
return x.UserLevel
}
return 0
}
var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor
var file_proxy_dokodemo_config_proto_rawDesc = []byte{
0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f,
0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65,
0x6d, 0x6f, 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, 0x18, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb4, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x12, 0x35, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 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, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x43, 0x0a, 0x0c,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74,
0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73,
0x74, 0x12, 0x34, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x07, 0x20,
0x03, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f,
0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69,
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x5f,
0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e,
0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 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, 0x42, 0x5e, 0x0a,
0x17, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 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, 0x64,
0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0xaa, 0x02, 0x13, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x44, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_dokodemo_config_proto_rawDescOnce sync.Once
file_proxy_dokodemo_config_proto_rawDescData = file_proxy_dokodemo_config_proto_rawDesc
)
func file_proxy_dokodemo_config_proto_rawDescGZIP() []byte {
file_proxy_dokodemo_config_proto_rawDescOnce.Do(func() {
file_proxy_dokodemo_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_dokodemo_config_proto_rawDescData)
})
return file_proxy_dokodemo_config_proto_rawDescData
}
var file_proxy_dokodemo_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_dokodemo_config_proto_goTypes = []interface{}{
(*Config)(nil), // 0: xray.proxy.dokodemo.Config
(*net.IPOrDomain)(nil), // 1: xray.common.net.IPOrDomain
(*net.NetworkList)(nil), // 2: xray.common.net.NetworkList
(net.Network)(0), // 3: xray.common.net.Network
}
var file_proxy_dokodemo_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.dokodemo.Config.address:type_name -> xray.common.net.IPOrDomain
2, // 1: xray.proxy.dokodemo.Config.network_list:type_name -> xray.common.net.NetworkList
3, // 2: xray.proxy.dokodemo.Config.networks:type_name -> xray.common.net.Network
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_dokodemo_config_proto_init() }
func file_proxy_dokodemo_config_proto_init() {
if File_proxy_dokodemo_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_dokodemo_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); 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_dokodemo_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_dokodemo_config_proto_goTypes,
DependencyIndexes: file_proxy_dokodemo_config_proto_depIdxs,
MessageInfos: file_proxy_dokodemo_config_proto_msgTypes,
}.Build()
File_proxy_dokodemo_config_proto = out.File
file_proxy_dokodemo_config_proto_rawDesc = nil
file_proxy_dokodemo_config_proto_goTypes = nil
file_proxy_dokodemo_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,25 @@
syntax = "proto3";
package xray.proxy.dokodemo;
option csharp_namespace = "Xray.Proxy.Dokodemo";
option go_package = "github.com/xtls/xray-core/v1/proxy/dokodemo";
option java_package = "com.xray.proxy.dokodemo";
option java_multiple_files = true;
import "common/net/address.proto";
import "common/net/network.proto";
message Config {
xray.common.net.IPOrDomain address = 1;
uint32 port = 2;
// List of networks that the Dokodemo accepts.
// Deprecated. Use networks.
xray.common.net.NetworkList network_list = 3 [deprecated = true];
// List of networks that the Dokodemo accepts.
repeated xray.common.net.Network networks = 7;
uint32 timeout = 4 [deprecated = true];
bool follow_redirect = 5;
uint32 user_level = 6;
}

214
proxy/dokodemo/dokodemo.go Normal file
View file

@ -0,0 +1,214 @@
// +build !confonly
package dokodemo
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
import (
"context"
"sync/atomic"
"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"
"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/features/routing"
"github.com/xtls/xray-core/v1/transport/internet"
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
d := new(DokodemoDoor)
err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
return d.Init(config.(*Config), pm, session.SockoptFromContext(ctx))
})
return d, err
}))
}
type DokodemoDoor struct {
policyManager policy.Manager
config *Config
address net.Address
port net.Port
sockopt *session.Sockopt
}
// Init initializes the DokodemoDoor instance with necessary parameters.
func (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.Sockopt) error {
if (config.NetworkList == nil || len(config.NetworkList.Network) == 0) && len(config.Networks) == 0 {
return newError("no network specified")
}
d.config = config
d.address = config.GetPredefinedAddress()
d.port = net.Port(config.Port)
d.policyManager = pm
d.sockopt = sockopt
return nil
}
// Network implements proxy.Inbound.
func (d *DokodemoDoor) Network() []net.Network {
if len(d.config.Networks) > 0 {
return d.config.Networks
}
return d.config.NetworkList.Network
}
func (d *DokodemoDoor) policy() policy.Session {
config := d.config
p := d.policyManager.ForLevel(config.UserLevel)
if config.Timeout > 0 && config.UserLevel == 0 {
p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
}
return p
}
type hasHandshakeAddress interface {
HandshakeAddress() net.Address
}
// Process implements proxy.Inbound.
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog(session.ExportIDToError(ctx))
dest := net.Destination{
Network: network,
Address: d.address,
Port: d.port,
}
destinationOverridden := false
if d.config.FollowRedirect {
if outbound := session.OutboundFromContext(ctx); outbound != nil && outbound.Target.IsValid() {
dest = outbound.Target
destinationOverridden = true
} else if handshake, ok := conn.(hasHandshakeAddress); ok {
addr := handshake.HandshakeAddress()
if addr != nil {
dest.Address = addr
destinationOverridden = true
}
}
}
if !dest.IsValid() || dest.Address == nil {
return newError("unable to get destination")
}
if inbound := session.InboundFromContext(ctx); inbound != nil {
inbound.User = &protocol.MemoryUser{
Level: d.config.UserLevel,
}
}
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(),
To: dest,
Status: log.AccessAccepted,
Reason: "",
})
newError("received request for ", conn.RemoteAddr()).WriteToLog(session.ExportIDToError(ctx))
plcy := d.policy()
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return newError("failed to dispatch request").Base(err)
}
requestCount := int32(1)
requestDone := func() error {
defer func() {
if atomic.AddInt32(&requestCount, -1) == 0 {
timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
}
}()
var reader buf.Reader
if dest.Network == net.Network_UDP {
reader = buf.NewPacketReader(conn)
} else {
reader = buf.NewReader(conn)
}
if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport request").Base(err)
}
return nil
}
tproxyRequest := func() error {
return nil
}
var writer buf.Writer
if network == net.Network_TCP {
writer = buf.NewWriter(conn)
} else {
// if we are in TPROXY mode, use linux's udp forging functionality
if !destinationOverridden {
writer = &buf.SequentialWriter{Writer: conn}
} else {
sockopt := &internet.SocketConfig{
Tproxy: internet.SocketConfig_TProxy,
}
if dest.Address.Family().IsIP() {
sockopt.BindAddress = dest.Address.IP()
sockopt.BindPort = uint32(dest.Port)
}
if d.sockopt != nil {
sockopt.Mark = d.sockopt.Mark
}
tConn, err := internet.DialSystem(ctx, net.DestinationFromAddr(conn.RemoteAddr()), sockopt)
if err != nil {
return err
}
defer tConn.Close()
writer = &buf.SequentialWriter{Writer: tConn}
tReader := buf.NewPacketReader(tConn)
requestCount++
tproxyRequest = func() error {
defer func() {
if atomic.AddInt32(&requestCount, -1) == 0 {
timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
}
}()
if err := buf.Copy(tReader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport request (TPROXY conn)").Base(err)
}
return nil
}
}
}
responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport response").Base(err)
}
return nil
}
if err := task.Run(ctx, task.OnSuccess(func() error {
return task.Run(ctx, requestDone, tproxyRequest)
}, task.Close(link.Writer)), responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return newError("connection ends").Base(err)
}
return nil
}

View file

@ -0,0 +1,9 @@
package dokodemo
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

5
proxy/freedom/config.go Normal file
View file

@ -0,0 +1,5 @@
package freedom
func (c *Config) useIP() bool {
return c.DomainStrategy == Config_USE_IP || c.DomainStrategy == Config_USE_IP4 || c.DomainStrategy == Config_USE_IP6
}

324
proxy/freedom/config.pb.go Normal file
View file

@ -0,0 +1,324 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/freedom/config.proto
package freedom
import (
proto "github.com/golang/protobuf/proto"
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
type Config_DomainStrategy int32
const (
Config_AS_IS Config_DomainStrategy = 0
Config_USE_IP Config_DomainStrategy = 1
Config_USE_IP4 Config_DomainStrategy = 2
Config_USE_IP6 Config_DomainStrategy = 3
)
// Enum value maps for Config_DomainStrategy.
var (
Config_DomainStrategy_name = map[int32]string{
0: "AS_IS",
1: "USE_IP",
2: "USE_IP4",
3: "USE_IP6",
}
Config_DomainStrategy_value = map[string]int32{
"AS_IS": 0,
"USE_IP": 1,
"USE_IP4": 2,
"USE_IP6": 3,
}
)
func (x Config_DomainStrategy) Enum() *Config_DomainStrategy {
p := new(Config_DomainStrategy)
*p = x
return p
}
func (x Config_DomainStrategy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
return file_proxy_freedom_config_proto_enumTypes[0].Descriptor()
}
func (Config_DomainStrategy) Type() protoreflect.EnumType {
return &file_proxy_freedom_config_proto_enumTypes[0]
}
func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Config_DomainStrategy.Descriptor instead.
func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1, 0}
}
type DestinationOverride struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
}
func (x *DestinationOverride) Reset() {
*x = DestinationOverride{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_freedom_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DestinationOverride) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DestinationOverride) ProtoMessage() {}
func (x *DestinationOverride) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_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 DestinationOverride.ProtoReflect.Descriptor instead.
func (*DestinationOverride) Descriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{0}
}
func (x *DestinationOverride) GetServer() *protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DomainStrategy Config_DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.freedom.Config_DomainStrategy" json:"domain_strategy,omitempty"`
// Deprecated: Do not use.
Timeout uint32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"`
DestinationOverride *DestinationOverride `protobuf:"bytes,3,opt,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
UserLevel uint32 `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_freedom_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_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 Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1}
}
func (x *Config) GetDomainStrategy() Config_DomainStrategy {
if x != nil {
return x.DomainStrategy
}
return Config_AS_IS
}
// Deprecated: Do not use.
func (x *Config) GetTimeout() uint32 {
if x != nil {
return x.Timeout
}
return 0
}
func (x *Config) GetDestinationOverride() *DestinationOverride {
if x != nil {
return x.DestinationOverride
}
return nil
}
func (x *Config) GetUserLevel() uint32 {
if x != nil {
return x.UserLevel
}
return 0
}
var File_proxy_freedom_config_proto protoreflect.FileDescriptor
var file_proxy_freedom_config_proto_rawDesc = []byte{
0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2f,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d,
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, 0x53, 0x0a, 0x13, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 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, 0x22, 0xb8, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74,
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f,
0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f,
0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69,
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x5a, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x13, 0x64, 0x65,
0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x22, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a,
0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
0x36, 0x10, 0x03, 0x42, 0x5b, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a,
0x2a, 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, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x12, 0x58, 0x72,
0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_freedom_config_proto_rawDescOnce sync.Once
file_proxy_freedom_config_proto_rawDescData = file_proxy_freedom_config_proto_rawDesc
)
func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
file_proxy_freedom_config_proto_rawDescOnce.Do(func() {
file_proxy_freedom_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_freedom_config_proto_rawDescData)
})
return file_proxy_freedom_config_proto_rawDescData
}
var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_freedom_config_proto_goTypes = []interface{}{
(Config_DomainStrategy)(0), // 0: xray.proxy.freedom.Config.DomainStrategy
(*DestinationOverride)(nil), // 1: xray.proxy.freedom.DestinationOverride
(*Config)(nil), // 2: xray.proxy.freedom.Config
(*protocol.ServerEndpoint)(nil), // 3: xray.common.protocol.ServerEndpoint
}
var file_proxy_freedom_config_proto_depIdxs = []int32{
3, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
0, // 1: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.proxy.freedom.Config.DomainStrategy
1, // 2: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
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_freedom_config_proto_init() }
func file_proxy_freedom_config_proto_init() {
if File_proxy_freedom_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_freedom_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DestinationOverride); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_freedom_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); 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_freedom_config_proto_rawDesc,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_freedom_config_proto_goTypes,
DependencyIndexes: file_proxy_freedom_config_proto_depIdxs,
EnumInfos: file_proxy_freedom_config_proto_enumTypes,
MessageInfos: file_proxy_freedom_config_proto_msgTypes,
}.Build()
File_proxy_freedom_config_proto = out.File
file_proxy_freedom_config_proto_rawDesc = nil
file_proxy_freedom_config_proto_goTypes = nil
file_proxy_freedom_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,26 @@
syntax = "proto3";
package xray.proxy.freedom;
option csharp_namespace = "Xray.Proxy.Freedom";
option go_package = "github.com/xtls/xray-core/v1/proxy/freedom";
option java_package = "com.xray.proxy.freedom";
option java_multiple_files = true;
import "common/protocol/server_spec.proto";
message DestinationOverride {
xray.common.protocol.ServerEndpoint server = 1;
}
message Config {
enum DomainStrategy {
AS_IS = 0;
USE_IP = 1;
USE_IP4 = 2;
USE_IP6 = 3;
}
DomainStrategy domain_strategy = 1;
uint32 timeout = 2 [deprecated = true];
DestinationOverride destination_override = 3;
uint32 user_level = 4;
}

View file

@ -0,0 +1,9 @@
package freedom
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

184
proxy/freedom/freedom.go Normal file
View file

@ -0,0 +1,184 @@
// +build !confonly
package freedom
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
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/dice"
"github.com/xtls/xray-core/v1/common/net"
"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/dns"
"github.com/xtls/xray-core/v1/features/policy"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler)
if err := core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) error {
return h.Init(config.(*Config), pm, d)
}); err != nil {
return nil, err
}
return h, nil
}))
}
// Handler handles Freedom connections.
type Handler struct {
policyManager policy.Manager
dns dns.Client
config *Config
}
// Init initializes the Handler with necessary parameters.
func (h *Handler) Init(config *Config, pm policy.Manager, d dns.Client) error {
h.config = config
h.policyManager = pm
h.dns = d
return nil
}
func (h *Handler) policy() policy.Session {
p := h.policyManager.ForLevel(h.config.UserLevel)
if h.config.Timeout > 0 && h.config.UserLevel == 0 {
p.Timeouts.ConnectionIdle = time.Duration(h.config.Timeout) * time.Second
}
return p
}
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
var lookupFunc func(string) ([]net.IP, error) = h.dns.LookupIP
if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) {
if lookupIPv4, ok := h.dns.(dns.IPv4Lookup); ok {
lookupFunc = lookupIPv4.LookupIPv4
}
} else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) {
if lookupIPv6, ok := h.dns.(dns.IPv6Lookup); ok {
lookupFunc = lookupIPv6.LookupIPv6
}
}
ips, err := lookupFunc(domain)
if err != nil {
newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
}
if len(ips) == 0 {
return nil
}
return net.IPAddress(ips[dice.Roll(len(ips))])
}
func isValidAddress(addr *net.IPOrDomain) bool {
if addr == nil {
return false
}
a := addr.AsAddress()
return a != net.AnyIP
}
// Process implements proxy.Outbound.
func (h *Handler) 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 h.config.DestinationOverride != nil {
server := h.config.DestinationOverride.Server
if isValidAddress(server.Address) {
destination.Address = server.Address.AsAddress()
}
if server.Port != 0 {
destination.Port = net.Port(server.Port)
}
}
newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx))
input := link.Reader
output := link.Writer
var conn internet.Connection
err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination
if h.config.useIP() && dialDest.Address.Family().IsDomain() {
ip := h.resolveIP(ctx, dialDest.Address.Domain(), dialer.Address())
if ip != nil {
dialDest = net.Destination{
Network: dialDest.Network,
Address: ip,
Port: dialDest.Port,
}
newError("dialing to to ", dialDest).WriteToLog(session.ExportIDToError(ctx))
}
}
rawConn, err := dialer.Dial(ctx, dialDest)
if err != nil {
return err
}
conn = rawConn
return nil
})
if err != nil {
return newError("failed to open connection to ", destination).Base(err)
}
defer conn.Close()
plcy := h.policy()
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
var writer buf.Writer
if destination.Network == net.Network_TCP {
writer = buf.NewWriter(conn)
} else {
writer = &buf.SequentialWriter{Writer: conn}
}
if err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to process request").Base(err)
}
return nil
}
responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
var reader buf.Reader
if destination.Network == net.Network_TCP {
reader = buf.NewReader(conn)
} else {
reader = buf.NewPacketReader(conn)
}
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
return newError("failed to process response").Base(err)
}
return nil
}
if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil {
return newError("connection ends").Base(err)
}
return nil
}

309
proxy/http/client.go Normal file
View file

@ -0,0 +1,309 @@
// +build !confonly
package http
import (
"bufio"
"context"
"encoding/base64"
"io"
"net/http"
"net/url"
"sync"
"golang.org/x/net/http2"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/bytespool"
"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"
"github.com/xtls/xray-core/v1/transport/internet/tls"
)
type Client struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
}
type h2Conn struct {
rawConn net.Conn
h2Conn *http2.ClientConn
}
var (
cachedH2Mutex sync.Mutex
cachedH2Conns map[net.Destination]h2Conn
)
// 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. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel.
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.")
}
target := outbound.Target
targetAddr := target.NetAddr()
if target.Network == net.Network_UDP {
return newError("UDP is not supported by HTTP outbound")
}
var user *protocol.MemoryUser
var conn internet.Connection
mbuf, _ := link.Reader.ReadMultiBuffer()
len := mbuf.Len()
firstPayload := bytespool.Alloc(len)
mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
firstPayload = firstPayload[:len]
buf.ReleaseMulti(mbuf)
defer bytespool.Free(firstPayload)
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)
if netConn != nil {
if _, ok := netConn.(*http2Conn); !ok {
if _, err := netConn.Write(firstPayload); err != nil {
netConn.Close()
return err
}
}
conn = internet.Connection(netConn)
}
return err
}); 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)
if user != nil {
p = c.policyManager.ForLevel(user.Level)
}
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
}
// 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) {
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: target},
Header: make(http.Header),
Host: target,
}
if user != nil && user.Account != nil {
account := user.Account.(*Account)
auth := account.GetUsername() + ":" + account.GetPassword()
req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
}
connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
req.Header.Set("Proxy-Connection", "Keep-Alive")
err := req.Write(rawConn)
if err != nil {
rawConn.Close()
return nil, err
}
resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
if err != nil {
rawConn.Close()
return nil, err
}
if resp.StatusCode != http.StatusOK {
rawConn.Close()
return nil, newError("Proxy responded with non 200 code: " + resp.Status)
}
return rawConn, nil
}
connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) {
pr, pw := io.Pipe()
req.Body = pr
var pErr error
var wg sync.WaitGroup
wg.Add(1)
go func() {
_, pErr = pw.Write(firstPayload)
wg.Done()
}()
resp, err := h2clientConn.RoundTrip(req)
if err != nil {
rawConn.Close()
return nil, err
}
wg.Wait()
if pErr != nil {
rawConn.Close()
return nil, pErr
}
if resp.StatusCode != http.StatusOK {
rawConn.Close()
return nil, newError("Proxy responded with non 200 code: " + resp.Status)
}
return newHTTP2Conn(rawConn, pw, resp.Body), nil
}
cachedH2Mutex.Lock()
cachedConn, cachedConnFound := cachedH2Conns[dest]
cachedH2Mutex.Unlock()
if cachedConnFound {
rc, cc := cachedConn.rawConn, cachedConn.h2Conn
if cc.CanTakeNewRequest() {
proxyConn, err := connectHTTP2(rc, cc)
if err != nil {
return nil, err
}
return proxyConn, nil
}
}
rawConn, err := dialer.Dial(ctx, dest)
if err != nil {
return nil, err
}
iConn := rawConn
if statConn, ok := iConn.(*internet.StatCouterConnection); ok {
iConn = statConn.Connection
}
nextProto := ""
if tlsConn, ok := iConn.(*tls.Conn); ok {
if err := tlsConn.Handshake(); err != nil {
rawConn.Close()
return nil, err
}
nextProto = tlsConn.ConnectionState().NegotiatedProtocol
}
switch nextProto {
case "", "http/1.1":
return connectHTTP1(rawConn)
case "h2":
t := http2.Transport{}
h2clientConn, err := t.NewClientConn(rawConn)
if err != nil {
rawConn.Close()
return nil, err
}
proxyConn, err := connectHTTP2(rawConn, h2clientConn)
if err != nil {
rawConn.Close()
return nil, err
}
cachedH2Mutex.Lock()
if cachedH2Conns == nil {
cachedH2Conns = make(map[net.Destination]h2Conn)
}
cachedH2Conns[dest] = h2Conn{
rawConn: rawConn,
h2Conn: h2clientConn,
}
cachedH2Mutex.Unlock()
return proxyConn, err
default:
return nil, newError("negotiated unsupported application layer protocol: " + nextProto)
}
}
func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn {
return &http2Conn{Conn: c, in: pipedReqBody, out: respBody}
}
type http2Conn struct {
net.Conn
in *io.PipeWriter
out io.ReadCloser
}
func (h *http2Conn) Read(p []byte) (n int, err error) {
return h.out.Read(p)
}
func (h *http2Conn) Write(p []byte) (n int, err error) {
return h.in.Write(p)
}
func (h *http2Conn) Close() error {
h.in.Close()
return h.out.Close()
}
func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig))
}))
}

28
proxy/http/config.go Normal file
View file

@ -0,0 +1,28 @@
package http
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 (sc *ServerConfig) HasAccount(username, password string) bool {
if sc.Accounts == nil {
return false
}
p, found := sc.Accounts[username]
if !found {
return false
}
return p == password
}

339
proxy/http/config.pb.go Normal file
View file

@ -0,0 +1,339 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/http/config.proto
package http
import (
proto "github.com/golang/protobuf/proto"
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
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_http_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_http_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_http_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 ""
}
// Config for HTTP proxy server.
type ServerConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Deprecated: Do not use.
Timeout uint32 `protobuf:"varint,1,opt,name=timeout,proto3" json:"timeout,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"`
AllowTransparent bool `protobuf:"varint,3,opt,name=allow_transparent,json=allowTransparent,proto3" json:"allow_transparent,omitempty"`
UserLevel uint32 `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
}
func (x *ServerConfig) Reset() {
*x = ServerConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_http_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_http_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_http_config_proto_rawDescGZIP(), []int{1}
}
// Deprecated: Do not use.
func (x *ServerConfig) GetTimeout() uint32 {
if x != nil {
return x.Timeout
}
return 0
}
func (x *ServerConfig) GetAccounts() map[string]string {
if x != nil {
return x.Accounts
}
return nil
}
func (x *ServerConfig) GetAllowTransparent() bool {
if x != nil {
return x.AllowTransparent
}
return false
}
func (x *ServerConfig) GetUserLevel() uint32 {
if x != nil {
return x.UserLevel
}
return 0
}
// ClientConfig is the protobuf config for HTTP proxy client.
type ClientConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Sever is a list of HTTP 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_http_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_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 ClientConfig.ProtoReflect.Descriptor instead.
func (*ClientConfig) Descriptor() ([]byte, []int) {
return file_proxy_http_config_proto_rawDescGZIP(), []int{2}
}
func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
var File_proxy_http_config_proto protoreflect.FileDescriptor
var file_proxy_http_config_proto_rawDesc = []byte{
0x0a, 0x17, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 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, 0xfe, 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12,
0x47, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68,
0x74, 0x74, 0x70, 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, 0x2b, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f,
0x77, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70,
0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65,
0x76, 0x65, 0x6c, 0x18, 0x04, 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, 0x42,
0x52, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78,
0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x27, 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, 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 (
file_proxy_http_config_proto_rawDescOnce sync.Once
file_proxy_http_config_proto_rawDescData = file_proxy_http_config_proto_rawDesc
)
func file_proxy_http_config_proto_rawDescGZIP() []byte {
file_proxy_http_config_proto_rawDescOnce.Do(func() {
file_proxy_http_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_http_config_proto_rawDescData)
})
return file_proxy_http_config_proto_rawDescData
}
var file_proxy_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
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
}
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
}
func init() { file_proxy_http_config_proto_init() }
func file_proxy_http_config_proto_init() {
if File_proxy_http_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_http_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_http_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_http_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_http_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_http_config_proto_goTypes,
DependencyIndexes: file_proxy_http_config_proto_depIdxs,
MessageInfos: file_proxy_http_config_proto_msgTypes,
}.Build()
File_proxy_http_config_proto = out.File
file_proxy_http_config_proto_rawDesc = nil
file_proxy_http_config_proto_goTypes = nil
file_proxy_http_config_proto_depIdxs = nil
}

28
proxy/http/config.proto Normal file
View file

@ -0,0 +1,28 @@
syntax = "proto3";
package xray.proxy.http;
option csharp_namespace = "Xray.Proxy.Http";
option go_package = "github.com/xtls/xray-core/v1/proxy/http";
option java_package = "com.xray.proxy.http";
option java_multiple_files = true;
import "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];
map<string, string> accounts = 2;
bool allow_transparent = 3;
uint32 user_level = 4;
}
// 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;
}

View file

@ -0,0 +1,9 @@
package http
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

3
proxy/http/http.go Normal file
View file

@ -0,0 +1,3 @@
package http
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen

329
proxy/http/server.go Normal file
View file

@ -0,0 +1,329 @@
// +build !confonly
package http
import (
"bufio"
"context"
"encoding/base64"
"io"
"net/http"
"strings"
"time"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/errors"
"github.com/xtls/xray-core/v1/common/log"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/protocol"
http_proto "github.com/xtls/xray-core/v1/common/protocol/http"
"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/features/routing"
"github.com/xtls/xray-core/v1/transport/internet"
)
// Server is an HTTP proxy server.
type Server struct {
config *ServerConfig
policyManager policy.Manager
}
// NewServer creates a new HTTP inbound handler.
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 && config.UserLevel == 0 {
p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
}
return p
}
// Network implements proxy.Inbound.
func (*Server) Network() []net.Network {
return []net.Network{net.Network_TCP, net.Network_UNIX}
}
func isTimeout(err error) bool {
nerr, ok := errors.Cause(err).(net.Error)
return ok && nerr.Timeout()
}
func parseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
return
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
type readerOnly struct {
io.Reader
}
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
inbound := session.InboundFromContext(ctx)
if inbound != nil {
inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel,
}
}
reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
Start:
if err := conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)); err != nil {
newError("failed to set read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
}
request, err := http.ReadRequest(reader)
if err != nil {
trace := newError("failed to read http request").Base(err)
if errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) {
trace.AtWarning()
}
return trace
}
if len(s.config.Accounts) > 0 {
user, pass, ok := parseBasicAuth(request.Header.Get("Proxy-Authorization"))
if !ok || !s.config.HasAccount(user, pass) {
return common.Error2(conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"proxy\"\r\n\r\n")))
}
if inbound != nil {
inbound.User.Email = user
}
}
newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]").WriteToLog(session.ExportIDToError(ctx))
if err := conn.SetReadDeadline(time.Time{}); err != nil {
newError("failed to clear read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
}
defaultPort := net.Port(80)
if strings.EqualFold(request.URL.Scheme, "https") {
defaultPort = net.Port(443)
}
host := request.Host
if host == "" {
host = request.URL.Host
}
dest, err := http_proto.ParseHost(host, defaultPort)
if err != nil {
return newError("malformed proxy host: ", host).AtWarning().Base(err)
}
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(),
To: request.URL,
Status: log.AccessAccepted,
Reason: "",
})
if strings.EqualFold(request.Method, "CONNECT") {
return s.handleConnect(ctx, request, reader, conn, dest, dispatcher)
}
keepAlive := (strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive")
err = s.handlePlainHTTP(ctx, request, conn, dest, dispatcher)
if err == errWaitAnother {
if keepAlive {
goto Start
}
err = nil
}
return err
}
func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher routing.Dispatcher) error {
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil {
return newError("failed to write back OK response").Base(err)
}
plcy := s.policy()
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return err
}
if reader.Buffered() > 0 {
payload, err := buf.ReadFrom(io.LimitReader(reader, int64(reader.Buffered())))
if err != nil {
return err
}
if err := link.Writer.WriteMultiBuffer(payload); err != nil {
return err
}
reader = nil
}
requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
}
responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(conn)
if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil {
return err
}
return nil
}
var closeWriter = task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, closeWriter, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return newError("connection ends").Base(err)
}
return nil
}
var errWaitAnother = newError("keep alive")
func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher) error {
if !s.config.AllowTransparent && request.URL.Host == "" {
// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.
response := &http.Response{
Status: "Bad Request",
StatusCode: 400,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: nil,
ContentLength: 0,
Close: true,
}
response.Header.Set("Proxy-Connection", "close")
response.Header.Set("Connection", "close")
return response.Write(writer)
}
if len(request.URL.Host) > 0 {
request.Host = request.URL.Host
}
http_proto.RemoveHopByHopHeaders(request.Header)
// Prevent UA from being set to golang's default ones
if request.Header.Get("User-Agent") == "" {
request.Header.Set("User-Agent", "")
}
content := &session.Content{
Protocol: "http/1.1",
}
content.SetAttribute(":method", strings.ToUpper(request.Method))
content.SetAttribute(":path", request.URL.Path)
for key := range request.Header {
value := request.Header.Get(key)
content.SetAttribute(strings.ToLower(key), value)
}
ctx = session.ContextWithContent(ctx, content)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return err
}
// Plain HTTP request is not a stream. The request always finishes before response. Hense request has to be closed later.
defer common.Close(link.Writer)
var result error = errWaitAnother
requestDone := func() error {
request.Header.Set("Connection", "close")
requestWriter := buf.NewBufferedWriter(link.Writer)
common.Must(requestWriter.SetBuffered(false))
if err := request.Write(requestWriter); err != nil {
return newError("failed to write whole request").Base(err).AtWarning()
}
return nil
}
responseDone := func() error {
responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size)
response, err := http.ReadResponse(responseReader, request)
if err == nil {
http_proto.RemoveHopByHopHeaders(response.Header)
if response.ContentLength >= 0 {
response.Header.Set("Proxy-Connection", "keep-alive")
response.Header.Set("Connection", "keep-alive")
response.Header.Set("Keep-Alive", "timeout=4")
response.Close = false
} else {
response.Close = true
result = nil
}
} else {
newError("failed to read response from ", request.Host).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
response = &http.Response{
Status: "Service Unavailable",
StatusCode: 503,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: nil,
ContentLength: 0,
Close: true,
}
response.Header.Set("Connection", "close")
response.Header.Set("Proxy-Connection", "close")
}
if err := response.Write(writer); err != nil {
return newError("failed to write response").Base(err).AtWarning()
}
return nil
}
if err := task.Run(ctx, requestDone, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return newError("connection ends").Base(err)
}
return result
}
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))
}))
}

150
proxy/mtproto/auth.go Normal file
View file

@ -0,0 +1,150 @@
package mtproto
import (
"context"
"crypto/rand"
"crypto/sha256"
"io"
"sync"
"github.com/xtls/xray-core/v1/common"
)
const (
HeaderSize = 64
)
type SessionContext struct {
ConnectionType [4]byte
DataCenterID uint16
}
func DefaultSessionContext() SessionContext {
return SessionContext{
ConnectionType: [4]byte{0xef, 0xef, 0xef, 0xef},
DataCenterID: 0,
}
}
type contextKey int32
const (
sessionContextKey contextKey = iota
)
func ContextWithSessionContext(ctx context.Context, c SessionContext) context.Context {
return context.WithValue(ctx, sessionContextKey, c)
}
func SessionContextFromContext(ctx context.Context) SessionContext {
if c := ctx.Value(sessionContextKey); c != nil {
return c.(SessionContext)
}
return DefaultSessionContext()
}
type Authentication struct {
Header [HeaderSize]byte
DecodingKey [32]byte
EncodingKey [32]byte
DecodingNonce [16]byte
EncodingNonce [16]byte
}
func (a *Authentication) DataCenterID() uint16 {
x := ((int16(a.Header[61]) << 8) | int16(a.Header[60]))
if x < 0 {
x = -x
}
return uint16(x) - 1
}
func (a *Authentication) ConnectionType() [4]byte {
var x [4]byte
copy(x[:], a.Header[56:60])
return x
}
func (a *Authentication) ApplySecret(b []byte) {
a.DecodingKey = sha256.Sum256(append(a.DecodingKey[:], b...))
a.EncodingKey = sha256.Sum256(append(a.EncodingKey[:], b...))
}
func generateRandomBytes(random []byte, connType [4]byte) {
for {
common.Must2(rand.Read(random))
if random[0] == 0xef {
continue
}
val := (uint32(random[3]) << 24) | (uint32(random[2]) << 16) | (uint32(random[1]) << 8) | uint32(random[0])
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
continue
}
if (uint32(random[7])<<24)|(uint32(random[6])<<16)|(uint32(random[5])<<8)|uint32(random[4]) == 0x00000000 {
continue
}
copy(random[56:60], connType[:])
return
}
}
func NewAuthentication(sc SessionContext) *Authentication {
auth := getAuthenticationObject()
random := auth.Header[:]
generateRandomBytes(random, sc.ConnectionType)
copy(auth.EncodingKey[:], random[8:])
copy(auth.EncodingNonce[:], random[8+32:])
keyivInverse := Inverse(random[8 : 8+32+16])
copy(auth.DecodingKey[:], keyivInverse)
copy(auth.DecodingNonce[:], keyivInverse[32:])
return auth
}
func ReadAuthentication(reader io.Reader) (*Authentication, error) {
auth := getAuthenticationObject()
if _, err := io.ReadFull(reader, auth.Header[:]); err != nil {
putAuthenticationObject(auth)
return nil, err
}
copy(auth.DecodingKey[:], auth.Header[8:])
copy(auth.DecodingNonce[:], auth.Header[8+32:])
keyivInverse := Inverse(auth.Header[8 : 8+32+16])
copy(auth.EncodingKey[:], keyivInverse)
copy(auth.EncodingNonce[:], keyivInverse[32:])
return auth, nil
}
// Inverse returns a new byte array. It is a sequence of bytes when the input is read from end to beginning.Inverse
// Visible for testing only.
func Inverse(b []byte) []byte {
lenb := len(b)
b2 := make([]byte, lenb)
for i, v := range b {
b2[lenb-i-1] = v
}
return b2
}
var (
authPool = sync.Pool{
New: func() interface{} {
return new(Authentication)
},
}
)
func getAuthenticationObject() *Authentication {
return authPool.Get().(*Authentication)
}
func putAuthenticationObject(auth *Authentication) {
authPool.Put(auth)
}

View file

@ -0,0 +1,53 @@
package mtproto_test
import (
"bytes"
"crypto/rand"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/xtls/xray-core/v1/common"
. "github.com/xtls/xray-core/v1/proxy/mtproto"
)
func TestInverse(t *testing.T) {
const size = 64
b := make([]byte, 64)
for b[0] == b[size-1] {
common.Must2(rand.Read(b))
}
bi := Inverse(b)
if b[0] == bi[0] {
t.Fatal("seems bytes are not inversed: ", b[0], "vs", bi[0])
}
bii := Inverse(bi)
if r := cmp.Diff(bii, b); r != "" {
t.Fatal(r)
}
}
func TestAuthenticationReadWrite(t *testing.T) {
a := NewAuthentication(DefaultSessionContext())
b := bytes.NewReader(a.Header[:])
a2, err := ReadAuthentication(b)
common.Must(err)
if r := cmp.Diff(a.EncodingKey[:], a2.DecodingKey[:]); r != "" {
t.Error("decoding key: ", r)
}
if r := cmp.Diff(a.EncodingNonce[:], a2.DecodingNonce[:]); r != "" {
t.Error("decoding nonce: ", r)
}
if r := cmp.Diff(a.DecodingKey[:], a2.EncodingKey[:]); r != "" {
t.Error("encoding key: ", r)
}
if r := cmp.Diff(a.DecodingNonce[:], a2.EncodingNonce[:]); r != "" {
t.Error("encoding nonce: ", r)
}
}

77
proxy/mtproto/client.go Normal file
View file

@ -0,0 +1,77 @@
package mtproto
import (
"context"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/crypto"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/session"
"github.com/xtls/xray-core/v1/common/task"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
)
type Client struct {
}
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
return &Client{}, nil
}
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("unknown destination.")
}
dest := outbound.Target
if dest.Network != net.Network_TCP {
return newError("not TCP traffic", dest)
}
conn, err := dialer.Dial(ctx, dest)
if err != nil {
return newError("failed to dial to ", dest).Base(err).AtWarning()
}
defer conn.Close()
sc := SessionContextFromContext(ctx)
auth := NewAuthentication(sc)
defer putAuthenticationObject(auth)
request := func() error {
encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:])
var header [HeaderSize]byte
encryptor.XORKeyStream(header[:], auth.Header[:])
copy(header[:56], auth.Header[:])
if _, err := conn.Write(header[:]); err != nil {
return newError("failed to write auth header").Base(err)
}
connWriter := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn))
return buf.Copy(link.Reader, connWriter)
}
response := func() error {
decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:])
connReader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn))
return buf.Copy(connReader, link.Writer)
}
var responseDoneAndCloseWriter = task.OnSuccess(response, task.Close(link.Writer))
if err := task.Run(ctx, request, responseDoneAndCloseWriter); 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))
}))
}

24
proxy/mtproto/config.go Normal file
View file

@ -0,0 +1,24 @@
package mtproto
import (
"github.com/xtls/xray-core/v1/common/protocol"
)
func (a *Account) Equals(another protocol.Account) bool {
aa, ok := another.(*Account)
if !ok {
return false
}
if len(a.Secret) != len(aa.Secret) {
return false
}
for i, v := range a.Secret {
if v != aa.Secret[i] {
return false
}
}
return true
}

277
proxy/mtproto/config.pb.go Normal file
View file

@ -0,0 +1,277 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/mtproto/config.proto
package mtproto
import (
proto "github.com/golang/protobuf/proto"
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
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Secret []byte `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_mtproto_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_mtproto_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_mtproto_config_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetSecret() []byte {
if x != nil {
return x.Secret
}
return nil
}
type ServerConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// User is a list of users that allowed to connect to this inbound.
// Although this is a repeated field, only the first user is effective for
// now.
User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"`
}
func (x *ServerConfig) Reset() {
*x = ServerConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_mtproto_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_mtproto_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_mtproto_config_proto_rawDescGZIP(), []int{1}
}
func (x *ServerConfig) GetUser() []*protocol.User {
if x != nil {
return x.User
}
return nil
}
type ClientConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ClientConfig) Reset() {
*x = ClientConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_mtproto_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_mtproto_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_mtproto_config_proto_rawDescGZIP(), []int{2}
}
var File_proxy_mtproto_config_proto protoreflect.FileDescriptor
var file_proxy_mtproto_config_proto_rawDesc = []byte{
0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x21, 0x0a, 0x07,
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22,
0x3e, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
0x2e, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22,
0x0e, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42,
0x5b, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78,
0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2a, 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,
0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x4d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_mtproto_config_proto_rawDescOnce sync.Once
file_proxy_mtproto_config_proto_rawDescData = file_proxy_mtproto_config_proto_rawDesc
)
func file_proxy_mtproto_config_proto_rawDescGZIP() []byte {
file_proxy_mtproto_config_proto_rawDescOnce.Do(func() {
file_proxy_mtproto_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_mtproto_config_proto_rawDescData)
})
return file_proxy_mtproto_config_proto_rawDescData
}
var file_proxy_mtproto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_proxy_mtproto_config_proto_goTypes = []interface{}{
(*Account)(nil), // 0: xray.proxy.mtproto.Account
(*ServerConfig)(nil), // 1: xray.proxy.mtproto.ServerConfig
(*ClientConfig)(nil), // 2: xray.proxy.mtproto.ClientConfig
(*protocol.User)(nil), // 3: xray.common.protocol.User
}
var file_proxy_mtproto_config_proto_depIdxs = []int32{
3, // 0: xray.proxy.mtproto.ServerConfig.user:type_name -> xray.common.protocol.User
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_mtproto_config_proto_init() }
func file_proxy_mtproto_config_proto_init() {
if File_proxy_mtproto_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_mtproto_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_mtproto_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_mtproto_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_mtproto_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_mtproto_config_proto_goTypes,
DependencyIndexes: file_proxy_mtproto_config_proto_depIdxs,
MessageInfos: file_proxy_mtproto_config_proto_msgTypes,
}.Build()
File_proxy_mtproto_config_proto = out.File
file_proxy_mtproto_config_proto_rawDesc = nil
file_proxy_mtproto_config_proto_goTypes = nil
file_proxy_mtproto_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,22 @@
syntax = "proto3";
package xray.proxy.mtproto;
option csharp_namespace = "Xray.Proxy.Mtproto";
option go_package = "github.com/xtls/xray-core/v1/proxy/mtproto";
option java_package = "com.xray.proxy.mtproto";
option java_multiple_files = true;
import "common/protocol/user.proto";
message Account {
bytes secret = 1;
}
message ServerConfig {
// User is a list of users that allowed to connect to this inbound.
// Although this is a repeated field, only the first user is effective for
// now.
repeated xray.common.protocol.User user = 1;
}
message ClientConfig {}

View file

@ -0,0 +1,9 @@
package mtproto
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

3
proxy/mtproto/mtproto.go Normal file
View file

@ -0,0 +1,3 @@
package mtproto
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen

162
proxy/mtproto/server.go Normal file
View file

@ -0,0 +1,162 @@
// +build !confonly
package mtproto
import (
"bytes"
"context"
"time"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/crypto"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/protocol"
"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/features/routing"
"github.com/xtls/xray-core/v1/transport/internet"
)
var (
dcList = []net.Address{
net.ParseAddress("149.154.175.50"),
net.ParseAddress("149.154.167.51"),
net.ParseAddress("149.154.175.100"),
net.ParseAddress("149.154.167.91"),
net.ParseAddress("149.154.171.5"),
}
)
type Server struct {
user *protocol.User
account *Account
policy policy.Manager
}
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
if len(config.User) == 0 {
return nil, newError("no user configured.")
}
user := config.User[0]
rawAccount, err := config.User[0].GetTypedAccount()
if err != nil {
return nil, newError("invalid account").Base(err)
}
account, ok := rawAccount.(*Account)
if !ok {
return nil, newError("not a MTProto account")
}
v := core.MustFromContext(ctx)
return &Server{
user: user,
account: account,
policy: v.GetFeature(policy.ManagerType()).(policy.Manager),
}, nil
}
func (s *Server) Network() []net.Network {
return []net.Network{net.Network_TCP}
}
var ctype1 = []byte{0xef, 0xef, 0xef, 0xef}
var ctype2 = []byte{0xee, 0xee, 0xee, 0xee}
func isValidConnectionType(c [4]byte) bool {
if bytes.Equal(c[:], ctype1) {
return true
}
if bytes.Equal(c[:], ctype2) {
return true
}
return false
}
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
sPolicy := s.policy.ForLevel(s.user.Level)
if err := conn.SetDeadline(time.Now().Add(sPolicy.Timeouts.Handshake)); err != nil {
newError("failed to set deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
}
auth, err := ReadAuthentication(conn)
if err != nil {
return newError("failed to read authentication header").Base(err)
}
defer putAuthenticationObject(auth)
if err := conn.SetDeadline(time.Time{}); err != nil {
newError("failed to clear deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
}
auth.ApplySecret(s.account.Secret)
decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:])
decryptor.XORKeyStream(auth.Header[:], auth.Header[:])
ct := auth.ConnectionType()
if !isValidConnectionType(ct) {
return newError("invalid connection type: ", ct)
}
dcID := auth.DataCenterID()
if dcID >= uint16(len(dcList)) {
return newError("invalid datacenter id: ", dcID)
}
dest := net.Destination{
Network: net.Network_TCP,
Address: dcList[dcID],
Port: net.Port(443),
}
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sPolicy.Buffer)
sc := SessionContext{
ConnectionType: ct,
DataCenterID: dcID,
}
ctx = ContextWithSessionContext(ctx, sc)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return newError("failed to dispatch request to: ", dest).Base(err)
}
request := func() error {
defer timer.SetTimeout(sPolicy.Timeouts.DownlinkOnly)
reader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn))
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
}
response := func() error {
defer timer.SetTimeout(sPolicy.Timeouts.UplinkOnly)
encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:])
writer := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn))
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
}
var responseDoneAndCloseWriter = task.OnSuccess(response, task.Close(link.Writer))
if err := task.Run(ctx, request, responseDoneAndCloseWriter); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return newError("connection ends").Base(err)
}
return nil
}
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))
}))
}

48
proxy/proxy.go Normal file
View file

@ -0,0 +1,48 @@
// Package proxy contains all proxies used by Xray.
//
// To implement an inbound or outbound proxy, one needs to do the following:
// 1. Implement the interface(s) below.
// 2. Register a config creator through common.RegisterConfig.
package proxy
import (
"context"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/features/routing"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
)
// An Inbound processes inbound connections.
type Inbound interface {
// Network returns a list of networks that this inbound supports. Connections with not-supported networks will not be passed into Process().
Network() []net.Network
// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.
Process(context.Context, net.Network, internet.Connection, routing.Dispatcher) error
}
// An Outbound process outbound connections.
type Outbound interface {
// Process processes the given connection. The given dialer may be used to dial a system outbound connection.
Process(context.Context, *transport.Link, internet.Dialer) error
}
// UserManager is the interface for Inbounds and Outbounds that can manage their users.
type UserManager interface {
// AddUser adds a new user.
AddUser(context.Context, *protocol.MemoryUser) error
// RemoveUser removes a user by email.
RemoveUser(context.Context, string) error
}
type GetInbound interface {
GetInbound() Inbound
}
type GetOutbound interface {
GetOutbound() Outbound
}

182
proxy/shadowsocks/client.go Normal file
View file

@ -0,0 +1,182 @@
// +build !confonly
package shadowsocks
import (
"context"
"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 inbound handler for Shadowsocks protocol
type Client struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
}
// NewClient create a new Shadowsocks client.
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 parse server spec").Base(err)
}
serverList.AddServer(s)
}
if serverList.Size() == 0 {
return nil, newError("0 server")
}
v := core.MustFromContext(ctx)
client := &Client{
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
return client, nil
}
// Process implements OutboundHandler.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
network := destination.Network
var server *protocol.ServerSpec
var conn internet.Connection
err := retry.ExponentialBackoff(5, 100).On(func() error {
server = c.serverPicker.PickServer()
dest := server.Destination()
dest.Network = network
rawConn, err := dialer.Dial(ctx, dest)
if err != nil {
return err
}
conn = rawConn
return nil
})
if err != nil {
return newError("failed to find an available destination").AtWarning().Base(err)
}
newError("tunneling request to ", destination, " via ", server.Destination()).WriteToLog(session.ExportIDToError(ctx))
defer conn.Close()
request := &protocol.RequestHeader{
Version: Version,
Address: destination.Address,
Port: destination.Port,
}
if destination.Network == net.Network_TCP {
request.Command = protocol.RequestCommandTCP
} else {
request.Command = protocol.RequestCommandUDP
}
user := server.PickUser()
_, ok := user.Account.(*MemoryAccount)
if !ok {
return newError("user account is not valid")
}
request.User = user
sessionPolicy := c.policyManager.ForLevel(user.Level)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
if request.Command == protocol.RequestCommandTCP {
bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
bodyWriter, err := WriteTCPRequest(request, bufferedWriter)
if err != nil {
return newError("failed to write request").Base(err)
}
if err := bufferedWriter.SetBuffered(false); err != nil {
return err
}
requestDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer))
}
responseDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
responseReader, err := ReadTCPResponse(user, conn)
if err != nil {
return err
}
return buf.Copy(responseReader, link.Writer, buf.UpdateActivity(timer))
}
var responseDoneAndCloseWriter = task.OnSuccess(responseDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
if request.Command == protocol.RequestCommandUDP {
writer := &buf.SequentialWriter{Writer: &UDPWriter{
Writer: conn,
Request: request,
}}
requestDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport all UDP request").Base(err)
}
return nil
}
responseDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
reader := &UDPReader{
Reader: conn,
User: user,
}
if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport all UDP response").Base(err)
}
return nil
}
var responseDoneAndCloseWriter = task.OnSuccess(responseDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
return nil
}
func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig))
}))
}

309
proxy/shadowsocks/config.go Normal file
View file

@ -0,0 +1,309 @@
package shadowsocks
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/sha1"
"io"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/crypto"
"github.com/xtls/xray-core/v1/common/protocol"
)
// MemoryAccount is an account type converted from Account.
type MemoryAccount struct {
Cipher Cipher
Key []byte
}
// Equals implements protocol.Account.Equals().
func (a *MemoryAccount) Equals(another protocol.Account) bool {
if account, ok := another.(*MemoryAccount); ok {
return bytes.Equal(a.Key, account.Key)
}
return false
}
func createAesGcm(key []byte) cipher.AEAD {
block, err := aes.NewCipher(key)
common.Must(err)
gcm, err := cipher.NewGCM(block)
common.Must(err)
return gcm
}
func createChacha20Poly1305(key []byte) cipher.AEAD {
chacha20, err := chacha20poly1305.New(key)
common.Must(err)
return chacha20
}
func (a *Account) getCipher() (Cipher, error) {
switch a.CipherType {
case CipherType_AES_128_CFB:
return &AesCfb{KeyBytes: 16}, nil
case CipherType_AES_256_CFB:
return &AesCfb{KeyBytes: 32}, nil
case CipherType_CHACHA20:
return &ChaCha20{IVBytes: 8}, nil
case CipherType_CHACHA20_IETF:
return &ChaCha20{IVBytes: 12}, nil
case CipherType_AES_128_GCM:
return &AEADCipher{
KeyBytes: 16,
IVBytes: 16,
AEADAuthCreator: createAesGcm,
}, nil
case CipherType_AES_256_GCM:
return &AEADCipher{
KeyBytes: 32,
IVBytes: 32,
AEADAuthCreator: createAesGcm,
}, nil
case CipherType_CHACHA20_POLY1305:
return &AEADCipher{
KeyBytes: 32,
IVBytes: 32,
AEADAuthCreator: createChacha20Poly1305,
}, nil
case CipherType_NONE:
return NoneCipher{}, nil
default:
return nil, newError("Unsupported cipher.")
}
}
// AsAccount implements protocol.AsAccount.
func (a *Account) AsAccount() (protocol.Account, error) {
cipher, err := a.getCipher()
if err != nil {
return nil, newError("failed to get cipher").Base(err)
}
return &MemoryAccount{
Cipher: cipher,
Key: passwordToCipherKey([]byte(a.Password), cipher.KeySize()),
}, nil
}
// Cipher is an interface for all Shadowsocks ciphers.
type Cipher interface {
KeySize() int32
IVSize() int32
NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error)
NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error)
IsAEAD() bool
EncodePacket(key []byte, b *buf.Buffer) error
DecodePacket(key []byte, b *buf.Buffer) error
}
// AesCfb represents all AES-CFB ciphers.
type AesCfb struct {
KeyBytes int32
}
func (*AesCfb) IsAEAD() bool {
return false
}
func (v *AesCfb) KeySize() int32 {
return v.KeyBytes
}
func (v *AesCfb) IVSize() int32 {
return 16
}
func (v *AesCfb) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {
stream := crypto.NewAesEncryptionStream(key, iv)
return &buf.SequentialWriter{Writer: crypto.NewCryptionWriter(stream, writer)}, nil
}
func (v *AesCfb) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {
stream := crypto.NewAesDecryptionStream(key, iv)
return &buf.SingleReader{
Reader: crypto.NewCryptionReader(stream, reader),
}, nil
}
func (v *AesCfb) EncodePacket(key []byte, b *buf.Buffer) error {
iv := b.BytesTo(v.IVSize())
stream := crypto.NewAesEncryptionStream(key, iv)
stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
return nil
}
func (v *AesCfb) DecodePacket(key []byte, b *buf.Buffer) error {
if b.Len() <= v.IVSize() {
return newError("insufficient data: ", b.Len())
}
iv := b.BytesTo(v.IVSize())
stream := crypto.NewAesDecryptionStream(key, iv)
stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
b.Advance(v.IVSize())
return nil
}
type AEADCipher struct {
KeyBytes int32
IVBytes int32
AEADAuthCreator func(key []byte) cipher.AEAD
}
func (*AEADCipher) IsAEAD() bool {
return true
}
func (c *AEADCipher) KeySize() int32 {
return c.KeyBytes
}
func (c *AEADCipher) IVSize() int32 {
return c.IVBytes
}
func (c *AEADCipher) createAuthenticator(key []byte, iv []byte) *crypto.AEADAuthenticator {
nonce := crypto.GenerateInitialAEADNonce()
subkey := make([]byte, c.KeyBytes)
hkdfSHA1(key, iv, subkey)
return &crypto.AEADAuthenticator{
AEAD: c.AEADAuthCreator(subkey),
NonceGenerator: nonce,
}
}
func (c *AEADCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {
auth := c.createAuthenticator(key, iv)
return crypto.NewAuthenticationWriter(auth, &crypto.AEADChunkSizeParser{
Auth: auth,
}, writer, protocol.TransferTypeStream, nil), nil
}
func (c *AEADCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {
auth := c.createAuthenticator(key, iv)
return crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{
Auth: auth,
}, reader, protocol.TransferTypeStream, nil), nil
}
func (c *AEADCipher) EncodePacket(key []byte, b *buf.Buffer) error {
ivLen := c.IVSize()
payloadLen := b.Len()
auth := c.createAuthenticator(key, b.BytesTo(ivLen))
b.Extend(int32(auth.Overhead()))
_, err := auth.Seal(b.BytesTo(ivLen), b.BytesRange(ivLen, payloadLen))
return err
}
func (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error {
if b.Len() <= c.IVSize() {
return newError("insufficient data: ", b.Len())
}
ivLen := c.IVSize()
payloadLen := b.Len()
auth := c.createAuthenticator(key, b.BytesTo(ivLen))
bbb, err := auth.Open(b.BytesTo(ivLen), b.BytesRange(ivLen, payloadLen))
if err != nil {
return err
}
b.Resize(ivLen, int32(len(bbb)))
return nil
}
type ChaCha20 struct {
IVBytes int32
}
func (*ChaCha20) IsAEAD() bool {
return false
}
func (v *ChaCha20) KeySize() int32 {
return 32
}
func (v *ChaCha20) IVSize() int32 {
return v.IVBytes
}
func (v *ChaCha20) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {
stream := crypto.NewChaCha20Stream(key, iv)
return &buf.SequentialWriter{Writer: crypto.NewCryptionWriter(stream, writer)}, nil
}
func (v *ChaCha20) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {
stream := crypto.NewChaCha20Stream(key, iv)
return &buf.SingleReader{Reader: crypto.NewCryptionReader(stream, reader)}, nil
}
func (v *ChaCha20) EncodePacket(key []byte, b *buf.Buffer) error {
iv := b.BytesTo(v.IVSize())
stream := crypto.NewChaCha20Stream(key, iv)
stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
return nil
}
func (v *ChaCha20) DecodePacket(key []byte, b *buf.Buffer) error {
if b.Len() <= v.IVSize() {
return newError("insufficient data: ", b.Len())
}
iv := b.BytesTo(v.IVSize())
stream := crypto.NewChaCha20Stream(key, iv)
stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
b.Advance(v.IVSize())
return nil
}
type NoneCipher struct{}
func (NoneCipher) KeySize() int32 { return 0 }
func (NoneCipher) IVSize() int32 { return 0 }
func (NoneCipher) IsAEAD() bool {
return true // to avoid OTA
}
func (NoneCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {
return buf.NewReader(reader), nil
}
func (NoneCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {
return buf.NewWriter(writer), nil
}
func (NoneCipher) EncodePacket(key []byte, b *buf.Buffer) error {
return nil
}
func (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error {
return nil
}
func passwordToCipherKey(password []byte, keySize int32) []byte {
key := make([]byte, 0, keySize)
md5Sum := md5.Sum(password)
key = append(key, md5Sum[:]...)
for int32(len(key)) < keySize {
md5Hash := md5.New()
common.Must2(md5Hash.Write(md5Sum[:]))
common.Must2(md5Hash.Write(password))
md5Hash.Sum(md5Sum[:0])
key = append(key, md5Sum[:]...)
}
return key
}
func hkdfSHA1(secret, salt, outkey []byte) {
r := hkdf.New(sha1.New, secret, salt, []byte("ss-subkey"))
common.Must2(io.ReadFull(r, outkey))
}

View file

@ -0,0 +1,417 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/shadowsocks/config.proto
package shadowsocks
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
type CipherType int32
const (
CipherType_UNKNOWN CipherType = 0
CipherType_AES_128_CFB CipherType = 1
CipherType_AES_256_CFB CipherType = 2
CipherType_CHACHA20 CipherType = 3
CipherType_CHACHA20_IETF CipherType = 4
CipherType_AES_128_GCM CipherType = 5
CipherType_AES_256_GCM CipherType = 6
CipherType_CHACHA20_POLY1305 CipherType = 7
CipherType_NONE CipherType = 8
)
// Enum value maps for CipherType.
var (
CipherType_name = map[int32]string{
0: "UNKNOWN",
1: "AES_128_CFB",
2: "AES_256_CFB",
3: "CHACHA20",
4: "CHACHA20_IETF",
5: "AES_128_GCM",
6: "AES_256_GCM",
7: "CHACHA20_POLY1305",
8: "NONE",
}
CipherType_value = map[string]int32{
"UNKNOWN": 0,
"AES_128_CFB": 1,
"AES_256_CFB": 2,
"CHACHA20": 3,
"CHACHA20_IETF": 4,
"AES_128_GCM": 5,
"AES_256_GCM": 6,
"CHACHA20_POLY1305": 7,
"NONE": 8,
}
)
func (x CipherType) Enum() *CipherType {
p := new(CipherType)
*p = x
return p
}
func (x CipherType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (CipherType) Descriptor() protoreflect.EnumDescriptor {
return file_proxy_shadowsocks_config_proto_enumTypes[0].Descriptor()
}
func (CipherType) Type() protoreflect.EnumType {
return &file_proxy_shadowsocks_config_proto_enumTypes[0]
}
func (x CipherType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use CipherType.Descriptor instead.
func (CipherType) EnumDescriptor() ([]byte, []int) {
return file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{0}
}
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"`
CipherType CipherType `protobuf:"varint,2,opt,name=cipher_type,json=cipherType,proto3,enum=xray.proxy.shadowsocks.CipherType" json:"cipher_type,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_shadowsocks_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_shadowsocks_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_shadowsocks_config_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *Account) GetCipherType() CipherType {
if x != nil {
return x.CipherType
}
return CipherType_UNKNOWN
}
type ServerConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// UdpEnabled specified whether or not to enable UDP for Shadowsocks.
// Deprecated. Use 'network' field.
//
// Deprecated: Do not use.
UdpEnabled bool `protobuf:"varint,1,opt,name=udp_enabled,json=udpEnabled,proto3" json:"udp_enabled,omitempty"`
User *protocol.User `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"`
Network []net.Network `protobuf:"varint,3,rep,packed,name=network,proto3,enum=xray.common.net.Network" json:"network,omitempty"`
}
func (x *ServerConfig) Reset() {
*x = ServerConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_shadowsocks_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_shadowsocks_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_shadowsocks_config_proto_rawDescGZIP(), []int{1}
}
// Deprecated: Do not use.
func (x *ServerConfig) GetUdpEnabled() bool {
if x != nil {
return x.UdpEnabled
}
return false
}
func (x *ServerConfig) GetUser() *protocol.User {
if x != nil {
return x.User
}
return nil
}
func (x *ServerConfig) GetNetwork() []net.Network {
if x != nil {
return x.Network
}
return nil
}
type ClientConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
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_shadowsocks_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_shadowsocks_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_shadowsocks_config_proto_rawDescGZIP(), []int{2}
}
func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
var File_proxy_shadowsocks_config_proto protoreflect.FileDescriptor
var file_proxy_shadowsocks_config_proto_rawDesc = []byte{
0x0a, 0x1e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f,
0x63, 0x6b, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x16, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61,
0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 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, 0x6a, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x43, 0x0a, 0x0b, 0x63, 0x69, 0x70, 0x68,
0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f,
0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70,
0x65, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x22, 0x97, 0x01,
0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23,
0x0a, 0x0b, 0x75, 0x64, 0x70, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x75, 0x64, 0x70, 0x45, 0x6e, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75,
0x73, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03,
0x20, 0x03, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 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, 0x9f, 0x01, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72,
0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f, 0x43, 0x46, 0x42,
0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x43, 0x46,
0x42, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x10,
0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x5f, 0x49, 0x45,
0x54, 0x46, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f,
0x47, 0x43, 0x4d, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36,
0x5f, 0x47, 0x43, 0x4d, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41,
0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x10, 0x07, 0x12, 0x08, 0x0a,
0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x08, 0x42, 0x67, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77,
0x73, 0x6f, 0x63, 0x6b, 0x73, 0x50, 0x01, 0x5a, 0x2e, 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, 0x68, 0x61, 0x64,
0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0xaa, 0x02, 0x16, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_shadowsocks_config_proto_rawDescOnce sync.Once
file_proxy_shadowsocks_config_proto_rawDescData = file_proxy_shadowsocks_config_proto_rawDesc
)
func file_proxy_shadowsocks_config_proto_rawDescGZIP() []byte {
file_proxy_shadowsocks_config_proto_rawDescOnce.Do(func() {
file_proxy_shadowsocks_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_shadowsocks_config_proto_rawDescData)
})
return file_proxy_shadowsocks_config_proto_rawDescData
}
var file_proxy_shadowsocks_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proxy_shadowsocks_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_proxy_shadowsocks_config_proto_goTypes = []interface{}{
(CipherType)(0), // 0: xray.proxy.shadowsocks.CipherType
(*Account)(nil), // 1: xray.proxy.shadowsocks.Account
(*ServerConfig)(nil), // 2: xray.proxy.shadowsocks.ServerConfig
(*ClientConfig)(nil), // 3: xray.proxy.shadowsocks.ClientConfig
(*protocol.User)(nil), // 4: xray.common.protocol.User
(net.Network)(0), // 5: xray.common.net.Network
(*protocol.ServerEndpoint)(nil), // 6: xray.common.protocol.ServerEndpoint
}
var file_proxy_shadowsocks_config_proto_depIdxs = []int32{
0, // 0: xray.proxy.shadowsocks.Account.cipher_type:type_name -> xray.proxy.shadowsocks.CipherType
4, // 1: xray.proxy.shadowsocks.ServerConfig.user:type_name -> xray.common.protocol.User
5, // 2: xray.proxy.shadowsocks.ServerConfig.network:type_name -> xray.common.net.Network
6, // 3: xray.proxy.shadowsocks.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_shadowsocks_config_proto_init() }
func file_proxy_shadowsocks_config_proto_init() {
if File_proxy_shadowsocks_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_shadowsocks_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_shadowsocks_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_shadowsocks_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_shadowsocks_config_proto_rawDesc,
NumEnums: 1,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_shadowsocks_config_proto_goTypes,
DependencyIndexes: file_proxy_shadowsocks_config_proto_depIdxs,
EnumInfos: file_proxy_shadowsocks_config_proto_enumTypes,
MessageInfos: file_proxy_shadowsocks_config_proto_msgTypes,
}.Build()
File_proxy_shadowsocks_config_proto = out.File
file_proxy_shadowsocks_config_proto_rawDesc = nil
file_proxy_shadowsocks_config_proto_goTypes = nil
file_proxy_shadowsocks_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,40 @@
syntax = "proto3";
package xray.proxy.shadowsocks;
option csharp_namespace = "Xray.Proxy.Shadowsocks";
option go_package = "github.com/xtls/xray-core/v1/proxy/shadowsocks";
option java_package = "com.xray.proxy.shadowsocks";
option java_multiple_files = true;
import "common/net/network.proto";
import "common/protocol/user.proto";
import "common/protocol/server_spec.proto";
message Account {
string password = 1;
CipherType cipher_type = 2;
}
enum CipherType {
UNKNOWN = 0;
AES_128_CFB = 1;
AES_256_CFB = 2;
CHACHA20 = 3;
CHACHA20_IETF = 4;
AES_128_GCM = 5;
AES_256_GCM = 6;
CHACHA20_POLY1305 = 7;
NONE = 8;
}
message ServerConfig {
// UdpEnabled specified whether or not to enable UDP for Shadowsocks.
// Deprecated. Use 'network' field.
bool udp_enabled = 1 [deprecated = true];
xray.common.protocol.User user = 2;
repeated xray.common.net.Network network = 3;
}
message ClientConfig {
repeated xray.common.protocol.ServerEndpoint server = 1;
}

View file

@ -0,0 +1,39 @@
package shadowsocks_test
import (
"crypto/rand"
"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/proxy/shadowsocks"
)
func TestAEADCipherUDP(t *testing.T) {
rawAccount := &shadowsocks.Account{
CipherType: shadowsocks.CipherType_AES_128_GCM,
Password: "test",
}
account, err := rawAccount.AsAccount()
common.Must(err)
cipher := account.(*shadowsocks.MemoryAccount).Cipher
key := make([]byte, cipher.KeySize())
common.Must2(rand.Read(key))
payload := make([]byte, 1024)
common.Must2(rand.Read(payload))
b1 := buf.New()
common.Must2(b1.ReadFullFrom(rand.Reader, cipher.IVSize()))
common.Must2(b1.Write(payload))
common.Must(cipher.EncodePacket(key, b1))
common.Must(cipher.DecodePacket(key, b1))
if diff := cmp.Diff(b1.Bytes(), payload); diff != "" {
t.Error(diff)
}
}

View file

@ -0,0 +1,9 @@
package shadowsocks
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View file

@ -0,0 +1,257 @@
// +build !confonly
package shadowsocks
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"hash/crc32"
"io"
"io/ioutil"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/dice"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/protocol"
)
const (
Version = 1
)
var addrParser = protocol.NewAddressParser(
protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),
protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),
protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),
protocol.WithAddressTypeParser(func(b byte) byte {
return b & 0x0F
}),
)
// ReadTCPSession reads a Shadowsocks TCP session from the given reader, returns its header and remaining parts.
func ReadTCPSession(user *protocol.MemoryUser, reader io.Reader) (*protocol.RequestHeader, buf.Reader, error) {
account := user.Account.(*MemoryAccount)
hashkdf := hmac.New(sha256.New, []byte("SSBSKDF"))
hashkdf.Write(account.Key)
behaviorSeed := crc32.ChecksumIEEE(hashkdf.Sum(nil))
behaviorRand := dice.NewDeterministicDice(int64(behaviorSeed))
BaseDrainSize := behaviorRand.Roll(3266)
RandDrainMax := behaviorRand.Roll(64) + 1
RandDrainRolled := dice.Roll(RandDrainMax)
DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled
readSizeRemain := DrainSize
buffer := buf.New()
defer buffer.Release()
ivLen := account.Cipher.IVSize()
var iv []byte
if ivLen > 0 {
if _, err := buffer.ReadFullFrom(reader, ivLen); err != nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to read IV").Base(err)
}
iv = append([]byte(nil), buffer.BytesTo(ivLen)...)
}
r, err := account.Cipher.NewDecryptionReader(account.Key, iv, reader)
if err != nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to initialize decoding stream").Base(err).AtError()
}
br := &buf.BufferedReader{Reader: r}
request := &protocol.RequestHeader{
Version: Version,
User: user,
Command: protocol.RequestCommandTCP,
}
readSizeRemain -= int(buffer.Len())
buffer.Clear()
addr, port, err := addrParser.ReadAddressPort(buffer, br)
if err != nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to read address").Base(err)
}
request.Address = addr
request.Port = port
if request.Address == nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("invalid remote address.")
}
return request, br, nil
}
func DrainConnN(reader io.Reader, n int) error {
_, err := io.CopyN(ioutil.Discard, reader, int64(n))
return err
}
// WriteTCPRequest writes Shadowsocks request into the given writer, and returns a writer for body.
func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {
user := request.User
account := user.Account.(*MemoryAccount)
var iv []byte
if account.Cipher.IVSize() > 0 {
iv = make([]byte, account.Cipher.IVSize())
common.Must2(rand.Read(iv))
if err := buf.WriteAllBytes(writer, iv); err != nil {
return nil, newError("failed to write IV")
}
}
w, err := account.Cipher.NewEncryptionWriter(account.Key, iv, writer)
if err != nil {
return nil, newError("failed to create encoding stream").Base(err).AtError()
}
header := buf.New()
if err := addrParser.WriteAddressPort(header, request.Address, request.Port); err != nil {
return nil, newError("failed to write address").Base(err)
}
if err := w.WriteMultiBuffer(buf.MultiBuffer{header}); err != nil {
return nil, newError("failed to write header").Base(err)
}
return w, nil
}
func ReadTCPResponse(user *protocol.MemoryUser, reader io.Reader) (buf.Reader, error) {
account := user.Account.(*MemoryAccount)
var iv []byte
if account.Cipher.IVSize() > 0 {
iv = make([]byte, account.Cipher.IVSize())
if _, err := io.ReadFull(reader, iv); err != nil {
return nil, newError("failed to read IV").Base(err)
}
}
return account.Cipher.NewDecryptionReader(account.Key, iv, reader)
}
func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {
user := request.User
account := user.Account.(*MemoryAccount)
var iv []byte
if account.Cipher.IVSize() > 0 {
iv = make([]byte, account.Cipher.IVSize())
common.Must2(rand.Read(iv))
if err := buf.WriteAllBytes(writer, iv); err != nil {
return nil, newError("failed to write IV.").Base(err)
}
}
return account.Cipher.NewEncryptionWriter(account.Key, iv, writer)
}
func EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buffer, error) {
user := request.User
account := user.Account.(*MemoryAccount)
buffer := buf.New()
ivLen := account.Cipher.IVSize()
if ivLen > 0 {
common.Must2(buffer.ReadFullFrom(rand.Reader, ivLen))
}
if err := addrParser.WriteAddressPort(buffer, request.Address, request.Port); err != nil {
return nil, newError("failed to write address").Base(err)
}
buffer.Write(payload)
if err := account.Cipher.EncodePacket(account.Key, buffer); err != nil {
return nil, newError("failed to encrypt UDP payload").Base(err)
}
return buffer, nil
}
func DecodeUDPPacket(user *protocol.MemoryUser, payload *buf.Buffer) (*protocol.RequestHeader, *buf.Buffer, error) {
account := user.Account.(*MemoryAccount)
var iv []byte
if !account.Cipher.IsAEAD() && account.Cipher.IVSize() > 0 {
// Keep track of IV as it gets removed from payload in DecodePacket.
iv = make([]byte, account.Cipher.IVSize())
copy(iv, payload.BytesTo(account.Cipher.IVSize()))
}
if err := account.Cipher.DecodePacket(account.Key, payload); err != nil {
return nil, nil, newError("failed to decrypt UDP payload").Base(err)
}
request := &protocol.RequestHeader{
Version: Version,
User: user,
Command: protocol.RequestCommandUDP,
}
payload.SetByte(0, payload.Byte(0)&0x0F)
addr, port, err := addrParser.ReadAddressPort(nil, payload)
if err != nil {
return nil, nil, newError("failed to parse address").Base(err)
}
request.Address = addr
request.Port = port
return request, payload, nil
}
type UDPReader struct {
Reader io.Reader
User *protocol.MemoryUser
}
func (v *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
buffer := buf.New()
_, err := buffer.ReadFrom(v.Reader)
if err != nil {
buffer.Release()
return nil, err
}
_, payload, err := DecodeUDPPacket(v.User, buffer)
if err != nil {
buffer.Release()
return nil, err
}
return buf.MultiBuffer{payload}, nil
}
type UDPWriter struct {
Writer io.Writer
Request *protocol.RequestHeader
}
// Write implements io.Writer.
func (w *UDPWriter) Write(payload []byte) (int, error) {
packet, err := EncodeUDPPacket(w.Request, payload)
if err != nil {
return 0, err
}
_, err = w.Writer.Write(packet.Bytes())
packet.Release()
return len(payload), err
}

View file

@ -0,0 +1,186 @@
package shadowsocks_test
import (
"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/shadowsocks"
)
func toAccount(a *Account) protocol.Account {
account, err := a.AsAccount()
common.Must(err)
return account
}
func TestUDPEncoding(t *testing.T) {
request := &protocol.RequestHeader{
Version: Version,
Command: protocol.RequestCommandUDP,
Address: net.LocalHostIP,
Port: 1234,
User: &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "shadowsocks-password",
CipherType: CipherType_AES_128_CFB,
}),
},
}
data := buf.New()
common.Must2(data.WriteString("test string"))
encodedData, err := EncodeUDPPacket(request, data.Bytes())
common.Must(err)
decodedRequest, decodedData, err := DecodeUDPPacket(request.User, encodedData)
common.Must(err)
if r := cmp.Diff(decodedData.Bytes(), data.Bytes()); r != "" {
t.Error("data: ", r)
}
if r := cmp.Diff(decodedRequest, request); r != "" {
t.Error("request: ", r)
}
}
func TestTCPRequest(t *testing.T) {
cases := []struct {
request *protocol.RequestHeader
payload []byte
}{
{
request: &protocol.RequestHeader{
Version: Version,
Command: protocol.RequestCommandTCP,
Address: net.LocalHostIP,
Port: 1234,
User: &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "tcp-password",
CipherType: CipherType_CHACHA20,
}),
},
},
payload: []byte("test string"),
},
{
request: &protocol.RequestHeader{
Version: Version,
Command: protocol.RequestCommandTCP,
Address: net.LocalHostIPv6,
Port: 1234,
User: &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "password",
CipherType: CipherType_AES_256_CFB,
}),
},
},
payload: []byte("test string"),
},
{
request: &protocol.RequestHeader{
Version: Version,
Command: protocol.RequestCommandTCP,
Address: net.DomainAddress("example.com"),
Port: 1234,
User: &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "password",
CipherType: CipherType_CHACHA20_IETF,
}),
},
},
payload: []byte("test string"),
},
}
runTest := func(request *protocol.RequestHeader, payload []byte) {
data := buf.New()
common.Must2(data.Write(payload))
cache := buf.New()
defer cache.Release()
writer, err := WriteTCPRequest(request, cache)
common.Must(err)
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data}))
decodedRequest, reader, err := ReadTCPSession(request.User, cache)
common.Must(err)
if r := cmp.Diff(decodedRequest, request); r != "" {
t.Error("request: ", r)
}
decodedData, err := reader.ReadMultiBuffer()
common.Must(err)
if r := cmp.Diff(decodedData[0].Bytes(), payload); r != "" {
t.Error("data: ", r)
}
}
for _, test := range cases {
runTest(test.request, test.payload)
}
}
func TestUDPReaderWriter(t *testing.T) {
user := &protocol.MemoryUser{
Account: toAccount(&Account{
Password: "test-password",
CipherType: CipherType_CHACHA20_IETF,
}),
}
cache := buf.New()
defer cache.Release()
writer := &buf.SequentialWriter{Writer: &UDPWriter{
Writer: cache,
Request: &protocol.RequestHeader{
Version: Version,
Address: net.DomainAddress("example.com"),
Port: 123,
User: user,
},
}}
reader := &UDPReader{
Reader: cache,
User: user,
}
{
b := buf.New()
common.Must2(b.WriteString("test payload"))
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))
payload, err := reader.ReadMultiBuffer()
common.Must(err)
if payload[0].String() != "test payload" {
t.Error("unexpected output: ", payload[0].String())
}
}
{
b := buf.New()
common.Must2(b.WriteString("test payload 2"))
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))
payload, err := reader.ReadMultiBuffer()
common.Must(err)
if payload[0].String() != "test payload 2" {
t.Error("unexpected output: ", payload[0].String())
}
}
}

239
proxy/shadowsocks/server.go Normal file
View file

@ -0,0 +1,239 @@
// +build !confonly
package shadowsocks
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/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/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"
)
type Server struct {
config *ServerConfig
user *protocol.MemoryUser
policyManager policy.Manager
}
// NewServer create a new Shadowsocks server.
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
if config.GetUser() == nil {
return nil, newError("user is not specified")
}
mUser, err := config.User.ToMemoryUser()
if err != nil {
return nil, newError("failed to parse user account").Base(err)
}
v := core.MustFromContext(ctx)
s := &Server{
config: config,
user: mUser,
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
return s, nil
}
func (s *Server) Network() []net.Network {
list := s.config.Network
if len(list) == 0 {
list = append(list, net.Network_TCP)
}
if s.config.UdpEnabled {
list = append(list, net.Network_UDP)
}
return list
}
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
switch network {
case net.Network_TCP:
return s.handleConnection(ctx, conn, dispatcher)
case net.Network_UDP:
return s.handlerUDPPayload(ctx, conn, dispatcher)
default:
return newError("unknown network: ", network)
}
}
func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error {
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
request := protocol.RequestHeaderFromContext(ctx)
if request == nil {
return
}
payload := packet.Payload
data, err := EncodeUDPPacket(request, payload.Bytes())
payload.Release()
if err != nil {
newError("failed to encode UDP packet").Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
return
}
defer data.Release()
conn.Write(data.Bytes())
})
inbound := session.InboundFromContext(ctx)
if inbound == nil {
panic("no inbound metadata")
}
inbound.User = s.user
reader := buf.NewPacketReader(conn)
for {
mpayload, err := reader.ReadMultiBuffer()
if err != nil {
break
}
for _, payload := range mpayload {
request, data, err := DecodeUDPPacket(s.user, payload)
if err != nil {
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() {
newError("dropping invalid UDP packet from: ", inbound.Source).Base(err).WriteToLog(session.ExportIDToError(ctx))
log.Record(&log.AccessMessage{
From: inbound.Source,
To: "",
Status: log.AccessRejected,
Reason: err,
})
}
payload.Release()
continue
}
currentPacketCtx := ctx
dest := request.Destination()
if inbound.Source.IsValid() {
currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: inbound.Source,
To: dest,
Status: log.AccessAccepted,
Reason: "",
Email: request.User.Email,
})
}
newError("tunnelling request to ", dest).WriteToLog(session.ExportIDToError(currentPacketCtx))
currentPacketCtx = protocol.ContextWithRequestHeader(currentPacketCtx, request)
udpServer.Dispatch(currentPacketCtx, dest, data)
}
}
return nil
}
func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error {
sessionPolicy := s.policyManager.ForLevel(s.user.Level)
conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake))
bufferedReader := buf.BufferedReader{Reader: buf.NewReader(conn)}
request, bodyReader, err := ReadTCPSession(s.user, &bufferedReader)
if err != nil {
log.Record(&log.AccessMessage{
From: conn.RemoteAddr(),
To: "",
Status: log.AccessRejected,
Reason: err,
})
return newError("failed to create request from: ", conn.RemoteAddr()).Base(err)
}
conn.SetReadDeadline(time.Time{})
inbound := session.InboundFromContext(ctx)
if inbound == nil {
panic("no inbound metadata")
}
inbound.User = s.user
dest := request.Destination()
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(),
To: dest,
Status: log.AccessAccepted,
Reason: "",
Email: request.User.Email,
})
newError("tunnelling request to ", dest).WriteToLog(session.ExportIDToError(ctx))
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return err
}
responseDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
responseWriter, err := WriteTCPResponse(request, bufferedWriter)
if err != nil {
return newError("failed to write response").Base(err)
}
{
payload, err := link.Reader.ReadMultiBuffer()
if err != nil {
return err
}
if err := responseWriter.WriteMultiBuffer(payload); err != nil {
return err
}
}
if err := bufferedWriter.SetBuffered(false); err != nil {
return err
}
if err := buf.Copy(link.Reader, responseWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport all TCP response").Base(err)
}
return nil
}
requestDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transport all TCP request").Base(err)
}
return nil
}
var requestDoneAndCloseWriter = task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDoneAndCloseWriter, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return newError("connection ends").Base(err)
}
return nil
}
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))
}))
}

View file

@ -0,0 +1,8 @@
// Package shadowsocks provides compatible functionality to Shadowsocks.
//
// Shadowsocks client and server are implemented as outbound and inbound respectively in Xray's term.
//
// R.I.P Shadowsocks
package shadowsocks
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen

154
proxy/socks/client.go Normal file
View 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
View 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
View 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
View 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;
}

View 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
View 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
}

View 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
View 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
View 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

212
proxy/trojan/client.go Normal file
View file

@ -0,0 +1,212 @@
// +build !confonly
package trojan
import (
"context"
"syscall"
"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/platform"
"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"
core "github.com/xtls/xray-core/v1/core"
"github.com/xtls/xray-core/v1/features/policy"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
"github.com/xtls/xray-core/v1/transport/internet/xtls"
)
// Client is a inbound handler for trojan protocol
type Client struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
}
// NewClient create a new trojan client.
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 parse server spec").Base(err)
}
serverList.AddServer(s)
}
if serverList.Size() == 0 {
return nil, newError("0 server")
}
v := core.MustFromContext(ctx)
client := &Client{
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
return client, nil
}
// Process implements OutboundHandler.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
network := destination.Network
var server *protocol.ServerSpec
var conn internet.Connection
err := retry.ExponentialBackoff(5, 100).On(func() error {
server = c.serverPicker.PickServer()
rawConn, err := dialer.Dial(ctx, server.Destination())
if err != nil {
return err
}
conn = rawConn
return nil
})
if err != nil {
return newError("failed to find an available destination").AtWarning().Base(err)
}
newError("tunneling request to ", destination, " via ", server.Destination()).WriteToLog(session.ExportIDToError(ctx))
defer conn.Close()
user := server.PickUser()
account, ok := user.Account.(*MemoryAccount)
if !ok {
return newError("user account is not valid")
}
iConn := conn
statConn, ok := iConn.(*internet.StatCouterConnection)
if ok {
iConn = statConn.Connection
}
var rawConn syscall.RawConn
connWriter := &ConnWriter{}
allowUDP443 := false
switch account.Flow {
case XRO + "-udp443", XRD + "-udp443":
allowUDP443 = true
account.Flow = account.Flow[:16]
fallthrough
case XRO, XRD:
if destination.Address.Family().IsDomain() && destination.Address.Domain() == muxCoolAddress {
return newError(account.Flow + " doesn't support Mux").AtWarning()
}
if destination.Network == net.Network_UDP {
if !allowUDP443 && destination.Port == 443 {
return newError(account.Flow + " stopped UDP/443").AtInfo()
}
} else { // enable XTLS only if making TCP request
if xtlsConn, ok := iConn.(*xtls.Conn); ok {
xtlsConn.RPRX = true
xtlsConn.SHOW = trojanXTLSShow
connWriter.Flow = account.Flow
if account.Flow == XRD {
xtlsConn.DirectMode = true
}
if sc, ok := xtlsConn.Connection.(syscall.Conn); ok {
rawConn, _ = sc.SyscallConn()
}
} else {
return newError(`failed to use ` + account.Flow + `, maybe "security" is not "xtls"`).AtWarning()
}
}
case "":
if _, ok := iConn.(*xtls.Conn); ok {
panic(`To avoid misunderstanding, you must fill in Trojan "flow" when using XTLS.`)
}
default:
return newError("unsupported flow " + account.Flow).AtWarning()
}
sessionPolicy := c.policyManager.ForLevel(user.Level)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
var bodyWriter buf.Writer
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
connWriter.Writer = bufferWriter
connWriter.Target = destination
connWriter.Account = account
if destination.Network == net.Network_UDP {
bodyWriter = &PacketWriter{Writer: connWriter, Target: destination}
} else {
bodyWriter = connWriter
}
// write some request payload to buffer
if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
return newError("failed to write A request payload").Base(err).AtWarning()
}
// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
if err = bufferWriter.SetBuffered(false); err != nil {
return newError("failed to flush payload").Base(err).AtWarning()
}
if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transfer request payload").Base(err).AtInfo()
}
return nil
}
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
var reader buf.Reader
if network == net.Network_UDP {
reader = &PacketReader{
Reader: conn,
}
} else {
reader = buf.NewReader(conn)
}
if rawConn != nil {
var counter stats.Counter
if statConn != nil {
counter = statConn.ReadCounter
}
return ReadV(reader, link.Writer, timer, iConn.(*xtls.Conn), rawConn, counter)
}
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
}
var responseDoneAndCloseWriter = task.OnSuccess(getResponse, task.Close(link.Writer))
if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); 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))
}))
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
xtlsShow := platform.NewEnvFlag("xray.trojan.xtls.show").GetValue(func() string { return defaultFlagValue })
if xtlsShow == "true" {
trojanXTLSShow = true
}
}

52
proxy/trojan/config.go Normal file
View file

@ -0,0 +1,52 @@
package trojan
import (
"crypto/sha256"
"encoding/hex"
fmt "fmt"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/protocol"
)
// MemoryAccount is an account type converted from Account.
type MemoryAccount struct {
Password string
Key []byte
Flow string
}
// AsAccount implements protocol.AsAccount.
func (a *Account) AsAccount() (protocol.Account, error) {
password := a.GetPassword()
key := hexSha224(password)
return &MemoryAccount{
Password: password,
Key: key,
Flow: a.Flow,
}, nil
}
// Equals implements protocol.Account.Equals().
func (a *MemoryAccount) Equals(another protocol.Account) bool {
if account, ok := another.(*MemoryAccount); ok {
return a.Password == account.Password
}
return false
}
func hexSha224(password string) []byte {
buf := make([]byte, 56)
hash := sha256.New224()
common.Must2(hash.Write([]byte(password)))
hex.Encode(buf, hash.Sum(nil))
return buf
}
func hexString(data []byte) string {
str := ""
for _, v := range data {
str += fmt.Sprintf("%02x", v)
}
return str
}

412
proxy/trojan/config.pb.go Normal file
View file

@ -0,0 +1,412 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/trojan/config.proto
package trojan
import (
proto "github.com/golang/protobuf/proto"
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
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"`
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_trojan_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_trojan_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_trojan_config_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *Account) GetFlow() string {
if x != nil {
return x.Flow
}
return ""
}
type Fallback struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Alpn string `protobuf:"bytes,1,opt,name=alpn,proto3" json:"alpn,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
Dest string `protobuf:"bytes,4,opt,name=dest,proto3" json:"dest,omitempty"`
Xver uint64 `protobuf:"varint,5,opt,name=xver,proto3" json:"xver,omitempty"`
}
func (x *Fallback) Reset() {
*x = Fallback{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_trojan_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Fallback) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Fallback) ProtoMessage() {}
func (x *Fallback) ProtoReflect() protoreflect.Message {
mi := &file_proxy_trojan_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 Fallback.ProtoReflect.Descriptor instead.
func (*Fallback) Descriptor() ([]byte, []int) {
return file_proxy_trojan_config_proto_rawDescGZIP(), []int{1}
}
func (x *Fallback) GetAlpn() string {
if x != nil {
return x.Alpn
}
return ""
}
func (x *Fallback) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *Fallback) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Fallback) GetDest() string {
if x != nil {
return x.Dest
}
return ""
}
func (x *Fallback) GetXver() uint64 {
if x != nil {
return x.Xver
}
return 0
}
type ClientConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
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_trojan_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_trojan_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_trojan_config_proto_rawDescGZIP(), []int{2}
}
func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
type ServerConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
}
func (x *ServerConfig) Reset() {
*x = ServerConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_trojan_config_proto_msgTypes[3]
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_trojan_config_proto_msgTypes[3]
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_trojan_config_proto_rawDescGZIP(), []int{3}
}
func (x *ServerConfig) GetUsers() []*protocol.User {
if x != nil {
return x.Users
}
return nil
}
func (x *ServerConfig) GetFallbacks() []*Fallback {
if x != nil {
return x.Fallbacks
}
return nil
}
var File_proxy_trojan_config_proto protoreflect.FileDescriptor
var file_proxy_trojan_config_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2f, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x1a, 0x1a,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f,
0x75, 0x73, 0x65, 0x72, 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, 0x39, 0x0a,
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x6e, 0x0a, 0x08, 0x46, 0x61, 0x6c, 0x6c,
0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01,
0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 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, 0x22, 0x7b, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65,
0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c,
0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e,
0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61,
0x63, 0x6b, 0x73, 0x42, 0x58, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x29,
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, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79,
0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_trojan_config_proto_rawDescOnce sync.Once
file_proxy_trojan_config_proto_rawDescData = file_proxy_trojan_config_proto_rawDesc
)
func file_proxy_trojan_config_proto_rawDescGZIP() []byte {
file_proxy_trojan_config_proto_rawDescOnce.Do(func() {
file_proxy_trojan_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_trojan_config_proto_rawDescData)
})
return file_proxy_trojan_config_proto_rawDescData
}
var file_proxy_trojan_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_proxy_trojan_config_proto_goTypes = []interface{}{
(*Account)(nil), // 0: xray.proxy.trojan.Account
(*Fallback)(nil), // 1: xray.proxy.trojan.Fallback
(*ClientConfig)(nil), // 2: xray.proxy.trojan.ClientConfig
(*ServerConfig)(nil), // 3: xray.proxy.trojan.ServerConfig
(*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint
(*protocol.User)(nil), // 5: xray.common.protocol.User
}
var file_proxy_trojan_config_proto_depIdxs = []int32{
4, // 0: xray.proxy.trojan.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
5, // 1: xray.proxy.trojan.ServerConfig.users:type_name -> xray.common.protocol.User
1, // 2: xray.proxy.trojan.ServerConfig.fallbacks:type_name -> xray.proxy.trojan.Fallback
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
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_trojan_config_proto_init() }
func file_proxy_trojan_config_proto_init() {
if File_proxy_trojan_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_trojan_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_trojan_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Fallback); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_trojan_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
}
}
file_proxy_trojan_config_proto_msgTypes[3].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
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proxy_trojan_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_trojan_config_proto_goTypes,
DependencyIndexes: file_proxy_trojan_config_proto_depIdxs,
MessageInfos: file_proxy_trojan_config_proto_msgTypes,
}.Build()
File_proxy_trojan_config_proto = out.File
file_proxy_trojan_config_proto_rawDesc = nil
file_proxy_trojan_config_proto_goTypes = nil
file_proxy_trojan_config_proto_depIdxs = nil
}

32
proxy/trojan/config.proto Normal file
View file

@ -0,0 +1,32 @@
syntax = "proto3";
package xray.proxy.trojan;
option csharp_namespace = "Xray.Proxy.Trojan";
option go_package = "github.com/xtls/xray-core/v1/proxy/trojan";
option java_package = "com.xray.proxy.trojan";
option java_multiple_files = true;
import "common/protocol/user.proto";
import "common/protocol/server_spec.proto";
message Account {
string password = 1;
string flow = 2;
}
message Fallback {
string alpn = 1;
string path = 2;
string type = 3;
string dest = 4;
uint64 xver = 5;
}
message ClientConfig {
repeated xray.common.protocol.ServerEndpoint server = 1;
}
message ServerConfig {
repeated xray.common.protocol.User users = 1;
repeated Fallback fallbacks = 3;
}

View file

@ -0,0 +1,9 @@
package trojan
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

341
proxy/trojan/protocol.go Normal file
View file

@ -0,0 +1,341 @@
package trojan
import (
"encoding/binary"
fmt "fmt"
"io"
"syscall"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/errors"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/common/signal"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/transport/internet/xtls"
)
var (
crlf = []byte{'\r', '\n'}
addrParser = protocol.NewAddressParser(
protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),
protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),
protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),
)
trojanXTLSShow = false
)
const (
maxLength = 8192
// XRD is constant for XTLS direct mode
XRD = "xtls-rprx-direct"
// XRO is constant for XTLS origin mode
XRO = "xtls-rprx-origin"
commandTCP byte = 1
commandUDP byte = 3
// for XTLS
commandXRD byte = 0xf0 // XTLS direct mode
commandXRO byte = 0xf1 // XTLS origin mode
)
// ConnWriter is TCP Connection Writer Wrapper for trojan protocol
type ConnWriter struct {
io.Writer
Target net.Destination
Account *MemoryAccount
Flow string
headerSent bool
}
// Write implements io.Writer
func (c *ConnWriter) Write(p []byte) (n int, err error) {
if !c.headerSent {
if err := c.writeHeader(); err != nil {
return 0, newError("failed to write request header").Base(err)
}
}
return c.Writer.Write(p)
}
// WriteMultiBuffer implements buf.Writer
func (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
defer buf.ReleaseMulti(mb)
for _, b := range mb {
if !b.IsEmpty() {
if _, err := c.Write(b.Bytes()); err != nil {
return err
}
}
}
return nil
}
func (c *ConnWriter) writeHeader() error {
buffer := buf.StackNew()
defer buffer.Release()
command := commandTCP
if c.Target.Network == net.Network_UDP {
command = commandUDP
} else if c.Flow == XRO {
command = commandXRO
} else if c.Flow == XRD {
command = commandXRD
}
if _, err := buffer.Write(c.Account.Key); err != nil {
return err
}
if _, err := buffer.Write(crlf); err != nil {
return err
}
if err := buffer.WriteByte(command); err != nil {
return err
}
if err := addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port); err != nil {
return err
}
if _, err := buffer.Write(crlf); err != nil {
return err
}
_, err := c.Writer.Write(buffer.Bytes())
if err == nil {
c.headerSent = true
}
return err
}
// PacketWriter UDP Connection Writer Wrapper for trojan protocol
type PacketWriter struct {
io.Writer
Target net.Destination
}
// WriteMultiBuffer implements buf.Writer
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
b := make([]byte, maxLength)
for !mb.IsEmpty() {
var length int
mb, length = buf.SplitBytes(mb, b)
if _, err := w.writePacket(b[:length], w.Target); err != nil {
buf.ReleaseMulti(mb)
return err
}
}
return nil
}
// WriteMultiBufferWithMetadata writes udp packet with destination specified
func (w *PacketWriter) WriteMultiBufferWithMetadata(mb buf.MultiBuffer, dest net.Destination) error {
b := make([]byte, maxLength)
for !mb.IsEmpty() {
var length int
mb, length = buf.SplitBytes(mb, b)
if _, err := w.writePacket(b[:length], dest); err != nil {
buf.ReleaseMulti(mb)
return err
}
}
return nil
}
func (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) {
buffer := buf.StackNew()
defer buffer.Release()
length := len(payload)
lengthBuf := [2]byte{}
binary.BigEndian.PutUint16(lengthBuf[:], uint16(length))
if err := addrParser.WriteAddressPort(&buffer, dest.Address, dest.Port); err != nil {
return 0, err
}
if _, err := buffer.Write(lengthBuf[:]); err != nil {
return 0, err
}
if _, err := buffer.Write(crlf); err != nil {
return 0, err
}
if _, err := buffer.Write(payload); err != nil {
return 0, err
}
_, err := w.Write(buffer.Bytes())
if err != nil {
return 0, err
}
return length, nil
}
// ConnReader is TCP Connection Reader Wrapper for trojan protocol
type ConnReader struct {
io.Reader
Target net.Destination
Flow string
headerParsed bool
}
// ParseHeader parses the trojan protocol header
func (c *ConnReader) ParseHeader() error {
var crlf [2]byte
var command [1]byte
var hash [56]byte
if _, err := io.ReadFull(c.Reader, hash[:]); err != nil {
return newError("failed to read user hash").Base(err)
}
if _, err := io.ReadFull(c.Reader, crlf[:]); err != nil {
return newError("failed to read crlf").Base(err)
}
if _, err := io.ReadFull(c.Reader, command[:]); err != nil {
return newError("failed to read command").Base(err)
}
network := net.Network_TCP
if command[0] == commandUDP {
network = net.Network_UDP
} else if command[0] == commandXRO {
c.Flow = XRO
} else if command[0] == commandXRD {
c.Flow = XRD
}
addr, port, err := addrParser.ReadAddressPort(nil, c.Reader)
if err != nil {
return newError("failed to read address and port").Base(err)
}
c.Target = net.Destination{Network: network, Address: addr, Port: port}
if _, err := io.ReadFull(c.Reader, crlf[:]); err != nil {
return newError("failed to read crlf").Base(err)
}
c.headerParsed = true
return nil
}
// Read implements io.Reader
func (c *ConnReader) Read(p []byte) (int, error) {
if !c.headerParsed {
if err := c.ParseHeader(); err != nil {
return 0, err
}
}
return c.Reader.Read(p)
}
// ReadMultiBuffer implements buf.Reader
func (c *ConnReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
_, err := b.ReadFrom(c)
return buf.MultiBuffer{b}, err
}
// PacketPayload combines udp payload and destination
type PacketPayload struct {
Target net.Destination
Buffer buf.MultiBuffer
}
// PacketReader is UDP Connection Reader Wrapper for trojan protocol
type PacketReader struct {
io.Reader
}
// ReadMultiBuffer implements buf.Reader
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
p, err := r.ReadMultiBufferWithMetadata()
if p != nil {
return p.Buffer, err
}
return nil, err
}
// ReadMultiBufferWithMetadata reads udp packet with destination
func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) {
addr, port, err := addrParser.ReadAddressPort(nil, r)
if err != nil {
return nil, newError("failed to read address and port").Base(err)
}
var lengthBuf [2]byte
if _, err := io.ReadFull(r, lengthBuf[:]); err != nil {
return nil, newError("failed to read payload length").Base(err)
}
remain := int(binary.BigEndian.Uint16(lengthBuf[:]))
if remain > maxLength {
return nil, newError("oversize payload")
}
var crlf [2]byte
if _, err := io.ReadFull(r, crlf[:]); err != nil {
return nil, newError("failed to read crlf").Base(err)
}
dest := net.UDPDestination(addr, port)
var mb buf.MultiBuffer
for remain > 0 {
length := buf.Size
if remain < length {
length = remain
}
b := buf.New()
mb = append(mb, b)
n, err := b.ReadFullFrom(r, int32(length))
if err != nil {
buf.ReleaseMulti(mb)
return nil, newError("failed to read payload").Base(err)
}
remain -= int(n)
}
return &PacketPayload{Target: dest, Buffer: mb}, nil
}
func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *xtls.Conn, rawConn syscall.RawConn, counter stats.Counter) error {
err := func() error {
var ct stats.Counter
for {
if conn.DirectIn {
conn.DirectIn = false
reader = buf.NewReadVReader(conn.Connection, rawConn)
ct = counter
if conn.SHOW {
fmt.Println(conn.MARK, "ReadV")
}
}
buffer, err := reader.ReadMultiBuffer()
if !buffer.IsEmpty() {
if ct != nil {
ct.Add(int64(buffer.Len()))
}
timer.Update()
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr
}
}
if err != nil {
return err
}
}
}()
if err != nil && errors.Cause(err) != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,91 @@
package trojan_test
import (
"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/trojan"
)
func toAccount(a *Account) protocol.Account {
account, err := a.AsAccount()
common.Must(err)
return account
}
func TestTCPRequest(t *testing.T) {
user := &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "password",
}),
}
payload := []byte("test string")
data := buf.New()
common.Must2(data.Write(payload))
buffer := buf.New()
defer buffer.Release()
destination := net.Destination{Network: net.Network_TCP, Address: net.LocalHostIP, Port: 1234}
writer := &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data}))
reader := &ConnReader{Reader: buffer}
common.Must(reader.ParseHeader())
if r := cmp.Diff(reader.Target, destination); r != "" {
t.Error("destination: ", r)
}
decodedData, err := reader.ReadMultiBuffer()
common.Must(err)
if r := cmp.Diff(decodedData[0].Bytes(), payload); r != "" {
t.Error("data: ", r)
}
}
func TestUDPRequest(t *testing.T) {
user := &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "password",
}),
}
payload := []byte("test string")
data := buf.New()
common.Must2(data.Write(payload))
buffer := buf.New()
defer buffer.Release()
destination := net.Destination{Network: net.Network_UDP, Address: net.LocalHostIP, Port: 1234}
writer := &PacketWriter{Writer: &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}, Target: destination}
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data}))
connReader := &ConnReader{Reader: buffer}
common.Must(connReader.ParseHeader())
packetReader := &PacketReader{Reader: connReader}
p, err := packetReader.ReadMultiBufferWithMetadata()
common.Must(err)
if p.Buffer.IsEmpty() {
t.Error("no request data")
}
if r := cmp.Diff(p.Target, destination); r != "" {
t.Error("destination: ", r)
}
mb, decoded := buf.SplitFirst(p.Buffer)
buf.ReleaseMulti(mb)
if r := cmp.Diff(decoded.Bytes(), payload); r != "" {
t.Error("data: ", r)
}
}

489
proxy/trojan/server.go Normal file
View file

@ -0,0 +1,489 @@
// +build !confonly
package trojan
import (
"context"
"crypto/tls"
"io"
"strconv"
"syscall"
"time"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/errors"
"github.com/xtls/xray-core/v1/common/log"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/platform"
"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/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"
core "github.com/xtls/xray-core/v1/core"
"github.com/xtls/xray-core/v1/features/policy"
"github.com/xtls/xray-core/v1/features/routing"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/transport/internet"
"github.com/xtls/xray-core/v1/transport/internet/udp"
"github.com/xtls/xray-core/v1/transport/internet/xtls"
)
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))
}))
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
xtlsShow := platform.NewEnvFlag("xray.trojan.xtls.show").GetValue(func() string { return defaultFlagValue })
if xtlsShow == "true" {
trojanXTLSShow = true
}
}
// Server is an inbound connection handler that handles messages in trojan protocol.
type Server struct {
policyManager policy.Manager
validator *Validator
fallbacks map[string]map[string]*Fallback // or nil
}
// NewServer creates a new trojan inbound handler.
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
validator := new(Validator)
for _, user := range config.Users {
u, err := user.ToMemoryUser()
if err != nil {
return nil, newError("failed to get trojan user").Base(err).AtError()
}
if err := validator.Add(u); err != nil {
return nil, newError("failed to add user").Base(err).AtError()
}
}
v := core.MustFromContext(ctx)
server := &Server{
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
validator: validator,
}
if config.Fallbacks != nil {
server.fallbacks = make(map[string]map[string]*Fallback)
for _, fb := range config.Fallbacks {
if server.fallbacks[fb.Alpn] == nil {
server.fallbacks[fb.Alpn] = make(map[string]*Fallback)
}
server.fallbacks[fb.Alpn][fb.Path] = fb
}
if server.fallbacks[""] != nil {
for alpn, pfb := range server.fallbacks {
if alpn != "" { // && alpn != "h2" {
for path, fb := range server.fallbacks[""] {
if pfb[path] == nil {
pfb[path] = fb
}
}
}
}
}
}
return server, nil
}
// AddUser implements proxy.UserManager.AddUser().
func (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {
return s.validator.Add(u)
}
// RemoveUser implements proxy.UserManager.RemoveUser().
func (s *Server) RemoveUser(ctx context.Context, e string) error {
return s.validator.Del(e)
}
// Network implements proxy.Inbound.Network().
func (s *Server) Network() []net.Network {
return []net.Network{net.Network_TCP, net.Network_UNIX}
}
// Process implements proxy.Inbound.Process().
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
sid := session.ExportIDToError(ctx)
iConn := conn
statConn, ok := iConn.(*internet.StatCouterConnection)
if ok {
iConn = statConn.Connection
}
sessionPolicy := s.policyManager.ForLevel(0)
if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return newError("unable to set read deadline").Base(err).AtWarning()
}
first := buf.New()
defer first.Release()
firstLen, err := first.ReadFrom(conn)
if err != nil {
return newError("failed to read first request").Base(err)
}
newError("firstLen = ", firstLen).AtInfo().WriteToLog(sid)
bufferedReader := &buf.BufferedReader{
Reader: buf.NewReader(conn),
Buffer: buf.MultiBuffer{first},
}
var user *protocol.MemoryUser
apfb := s.fallbacks
isfb := apfb != nil
shouldFallback := false
if firstLen < 58 || first.Byte(56) != '\r' {
// invalid protocol
err = newError("not trojan protocol")
log.Record(&log.AccessMessage{
From: conn.RemoteAddr(),
To: "",
Status: log.AccessRejected,
Reason: err,
})
shouldFallback = true
} else {
user = s.validator.Get(hexString(first.BytesTo(56)))
if user == nil {
// invalid user, let's fallback
err = newError("not a valid user")
log.Record(&log.AccessMessage{
From: conn.RemoteAddr(),
To: "",
Status: log.AccessRejected,
Reason: err,
})
shouldFallback = true
}
}
if isfb && shouldFallback {
return s.fallback(ctx, sid, err, sessionPolicy, conn, iConn, apfb, first, firstLen, bufferedReader)
} else if shouldFallback {
return newError("invalid protocol or invalid user")
}
clientReader := &ConnReader{Reader: bufferedReader}
if err := clientReader.ParseHeader(); err != nil {
log.Record(&log.AccessMessage{
From: conn.RemoteAddr(),
To: "",
Status: log.AccessRejected,
Reason: err,
})
return newError("failed to create request from: ", conn.RemoteAddr()).Base(err)
}
destination := clientReader.Target
if err := conn.SetReadDeadline(time.Time{}); err != nil {
return newError("unable to set read deadline").Base(err).AtWarning()
}
inbound := session.InboundFromContext(ctx)
if inbound == nil {
panic("no inbound metadata")
}
inbound.User = user
sessionPolicy = s.policyManager.ForLevel(user.Level)
if destination.Network == net.Network_UDP { // handle udp request
return s.handleUDPPayload(ctx, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher)
}
// handle tcp request
account, ok := user.Account.(*MemoryAccount)
if !ok {
return newError("user account is not valid")
}
var rawConn syscall.RawConn
switch clientReader.Flow {
case XRO, XRD:
if account.Flow == clientReader.Flow {
if destination.Address.Family().IsDomain() && destination.Address.Domain() == muxCoolAddress {
return newError(clientReader.Flow + " doesn't support Mux").AtWarning()
}
if xtlsConn, ok := iConn.(*xtls.Conn); ok {
xtlsConn.RPRX = true
xtlsConn.SHOW = trojanXTLSShow
if clientReader.Flow == XRD {
xtlsConn.DirectMode = true
if sc, ok := xtlsConn.Connection.(syscall.Conn); ok {
rawConn, _ = sc.SyscallConn()
}
}
} else {
return newError(`failed to use ` + clientReader.Flow + `, maybe "security" is not "xtls"`).AtWarning()
}
} else {
return newError("unable to use ", clientReader.Flow).AtWarning()
}
case "":
default:
return newError("unsupported flow " + account.Flow).AtWarning()
}
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(),
To: destination,
Status: log.AccessAccepted,
Reason: "",
Email: user.Email,
})
newError("received request for ", destination).WriteToLog(sid)
return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher, iConn, rawConn, statConn)
}
func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error {
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
common.Must(clientWriter.WriteMultiBufferWithMetadata(buf.MultiBuffer{packet.Payload}, packet.Source))
})
inbound := session.InboundFromContext(ctx)
user := inbound.User
for {
select {
case <-ctx.Done():
return nil
default:
p, err := clientReader.ReadMultiBufferWithMetadata()
if err != nil {
if errors.Cause(err) != io.EOF {
return newError("unexpected EOF").Base(err)
}
return nil
}
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: inbound.Source,
To: p.Target,
Status: log.AccessAccepted,
Reason: "",
Email: user.Email,
})
newError("tunnelling request to ", p.Target).WriteToLog(session.ExportIDToError(ctx))
for _, b := range p.Buffer {
udpServer.Dispatch(ctx, p.Target, b)
}
}
}
}
func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session,
destination net.Destination,
clientReader buf.Reader,
clientWriter buf.Writer, dispatcher routing.Dispatcher, iConn internet.Connection, rawConn syscall.RawConn, statConn *internet.StatCouterConnection) error {
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
link, err := dispatcher.Dispatch(ctx, destination)
if err != nil {
return newError("failed to dispatch request to ", destination).Base(err)
}
requestDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
var err error
if rawConn != nil {
var counter stats.Counter
if statConn != nil {
counter = statConn.ReadCounter
}
err = ReadV(clientReader, link.Writer, timer, iConn.(*xtls.Conn), rawConn, counter)
} else {
err = buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer))
}
if err != nil {
return newError("failed to transfer request").Base(err)
}
return nil
}
responseDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
if err := buf.Copy(link.Reader, clientWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to write response").Base(err)
}
return nil
}
var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
common.Must(common.Interrupt(link.Reader))
common.Must(common.Interrupt(link.Writer))
return newError("connection ends").Base(err)
}
return nil
}
func (s *Server) fallback(ctx context.Context, sid errors.ExportOption, err error, sessionPolicy policy.Session, connection internet.Connection, iConn internet.Connection, apfb map[string]map[string]*Fallback, first *buf.Buffer, firstLen int64, reader buf.Reader) error {
if err := connection.SetReadDeadline(time.Time{}); err != nil {
newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid)
}
newError("fallback starts").Base(err).AtInfo().WriteToLog(sid)
alpn := ""
if len(apfb) > 1 || apfb[""] == nil {
if tlsConn, ok := iConn.(*tls.Conn); ok {
alpn = tlsConn.ConnectionState().NegotiatedProtocol
newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid)
} else if xtlsConn, ok := iConn.(*xtls.Conn); ok {
alpn = xtlsConn.ConnectionState().NegotiatedProtocol
newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid)
}
if apfb[alpn] == nil {
alpn = ""
}
}
pfb := apfb[alpn]
if pfb == nil {
return newError(`failed to find the default "alpn" config`).AtWarning()
}
path := ""
if len(pfb) > 1 || pfb[""] == nil {
if firstLen >= 18 && first.Byte(4) != '*' { // not h2c
firstBytes := first.Bytes()
for i := 4; i <= 8; i++ { // 5 -> 9
if firstBytes[i] == '/' && firstBytes[i-1] == ' ' {
search := len(firstBytes)
if search > 64 {
search = 64 // up to about 60
}
for j := i + 1; j < search; j++ {
k := firstBytes[j]
if k == '\r' || k == '\n' { // avoid logging \r or \n
break
}
if k == ' ' {
path = string(firstBytes[i:j])
newError("realPath = " + path).AtInfo().WriteToLog(sid)
if pfb[path] == nil {
path = ""
}
break
}
}
break
}
}
}
}
fb := pfb[path]
if fb == nil {
return newError(`failed to find the default "path" config`).AtWarning()
}
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
var conn net.Conn
if err := retry.ExponentialBackoff(5, 100).On(func() error {
var dialer net.Dialer
conn, err = dialer.DialContext(ctx, fb.Type, fb.Dest)
if err != nil {
return err
}
return nil
}); err != nil {
return newError("failed to dial to " + fb.Dest).Base(err).AtWarning()
}
defer conn.Close()
serverReader := buf.NewReader(conn)
serverWriter := buf.NewWriter(conn)
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
if fb.Xver != 0 {
remoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String())
if err != nil {
return err
}
localAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String())
if err != nil {
return err
}
ipv4 := true
for i := 0; i < len(remoteAddr); i++ {
if remoteAddr[i] == ':' {
ipv4 = false
break
}
}
pro := buf.New()
defer pro.Release()
switch fb.Xver {
case 1:
if ipv4 {
common.Must2(pro.Write([]byte("PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n")))
} else {
common.Must2(pro.Write([]byte("PROXY TCP6 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n")))
}
case 2:
common.Must2(pro.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21"))) // signature + v2 + PROXY
if ipv4 {
common.Must2(pro.Write([]byte("\x11\x00\x0C"))) // AF_INET + STREAM + 12 bytes
common.Must2(pro.Write(net.ParseIP(remoteAddr).To4()))
common.Must2(pro.Write(net.ParseIP(localAddr).To4()))
} else {
common.Must2(pro.Write([]byte("\x21\x00\x24"))) // AF_INET6 + STREAM + 36 bytes
common.Must2(pro.Write(net.ParseIP(remoteAddr).To16()))
common.Must2(pro.Write(net.ParseIP(localAddr).To16()))
}
p1, _ := strconv.ParseUint(remotePort, 10, 16)
p2, _ := strconv.ParseUint(localPort, 10, 16)
common.Must2(pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)}))
}
if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil {
return newError("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning()
}
}
if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to fallback request payload").Base(err).AtInfo()
}
return nil
}
writer := buf.NewWriter(connection)
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to deliver response payload").Base(err).AtInfo()
}
return nil
}
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil {
common.Must(common.Interrupt(serverReader))
common.Must(common.Interrupt(serverWriter))
return newError("fallback ends").Base(err).AtInfo()
}
return nil
}

5
proxy/trojan/trojan.go Normal file
View file

@ -0,0 +1,5 @@
package trojan
const (
muxCoolAddress = "v1.mux.cool"
)

53
proxy/trojan/validator.go Normal file
View file

@ -0,0 +1,53 @@
// +build !confonly
package trojan
import (
"strings"
"sync"
"github.com/xtls/xray-core/v1/common/protocol"
)
// Validator stores valid trojan users.
type Validator struct {
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
email sync.Map
users sync.Map
}
// Add a trojan user, Email must be empty or unique.
func (v *Validator) Add(u *protocol.MemoryUser) error {
if u.Email != "" {
_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)
if loaded {
return newError("User ", u.Email, " already exists.")
}
}
v.users.Store(hexString(u.Account.(*MemoryAccount).Key), u)
return nil
}
// Del a trojan user with a non-empty Email.
func (v *Validator) Del(e string) error {
if e == "" {
return newError("Email must not be empty.")
}
le := strings.ToLower(e)
u, _ := v.email.Load(le)
if u == nil {
return newError("User ", e, " not found.")
}
v.email.Delete(le)
v.users.Delete(hexString(u.(*protocol.MemoryUser).Account.(*MemoryAccount).Key))
return nil
}
// Get a trojan user with hashed key, nil if user doesn't exist.
func (v *Validator) Get(hash string) *protocol.MemoryUser {
u, _ := v.users.Load(hash)
if u != nil {
return u.(*protocol.MemoryUser)
}
return nil
}

40
proxy/vless/account.go Normal file
View file

@ -0,0 +1,40 @@
// +build !confonly
package vless
import (
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/common/uuid"
)
// AsAccount implements protocol.Account.AsAccount().
func (a *Account) AsAccount() (protocol.Account, error) {
id, err := uuid.ParseString(a.Id)
if err != nil {
return nil, newError("failed to parse ID").Base(err).AtError()
}
return &MemoryAccount{
ID: protocol.NewID(id),
Flow: a.Flow, // needs parser here?
Encryption: a.Encryption, // needs parser here?
}, nil
}
// MemoryAccount is an in-memory form of VLess account.
type MemoryAccount struct {
// ID of the account.
ID *protocol.ID
// Flow of the account. May be "xtls-rprx-direct".
Flow string
// Encryption of the account. Used for client connections, and only accepts "none" for now.
Encryption string
}
// Equals implements protocol.Account.Equals().
func (a *MemoryAccount) Equals(account protocol.Account) bool {
vlessAccount, ok := account.(*MemoryAccount)
if !ok {
return false
}
return a.ID.Equals(vlessAccount.ID)
}

174
proxy/vless/account.pb.go Normal file
View file

@ -0,0 +1,174 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/vless/account.proto
package vless
import (
proto "github.com/golang/protobuf/proto"
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
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Flow settings. May be "xtls-rprx-direct".
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
// Encryption settings. Only applies to client side, and only accepts "none" for now.
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_vless_account_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_vless_account_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_vless_account_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Account) GetFlow() string {
if x != nil {
return x.Flow
}
return ""
}
func (x *Account) GetEncryption() string {
if x != nil {
return x.Encryption
}
return ""
}
var File_proxy_vless_account_proto protoreflect.FileDescriptor
var file_proxy_vless_account_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a,
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x55, 0x0a, 0x14,
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 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, 0x76, 0x6c, 0x65, 0x73, 0x73,
0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c,
0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_vless_account_proto_rawDescOnce sync.Once
file_proxy_vless_account_proto_rawDescData = file_proxy_vless_account_proto_rawDesc
)
func file_proxy_vless_account_proto_rawDescGZIP() []byte {
file_proxy_vless_account_proto_rawDescOnce.Do(func() {
file_proxy_vless_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vless_account_proto_rawDescData)
})
return file_proxy_vless_account_proto_rawDescData
}
var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_vless_account_proto_goTypes = []interface{}{
(*Account)(nil), // 0: xray.proxy.vless.Account
}
var file_proxy_vless_account_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_proxy_vless_account_proto_init() }
func file_proxy_vless_account_proto_init() {
if File_proxy_vless_account_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_vless_account_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
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proxy_vless_account_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_vless_account_proto_goTypes,
DependencyIndexes: file_proxy_vless_account_proto_depIdxs,
MessageInfos: file_proxy_vless_account_proto_msgTypes,
}.Build()
File_proxy_vless_account_proto = out.File
file_proxy_vless_account_proto_rawDesc = nil
file_proxy_vless_account_proto_goTypes = nil
file_proxy_vless_account_proto_depIdxs = nil
}

16
proxy/vless/account.proto Normal file
View file

@ -0,0 +1,16 @@
syntax = "proto3";
package xray.proxy.vless;
option csharp_namespace = "Xray.Proxy.Vless";
option go_package = "github.com/xtls/xray-core/v1/proxy/vless";
option java_package = "com.xray.proxy.vless";
option java_multiple_files = true;
message Account {
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
string id = 1;
// Flow settings. May be "xtls-rprx-direct".
string flow = 2;
// Encryption settings. Only applies to client side, and only accepts "none" for now.
string encryption = 3;
}

View file

@ -0,0 +1,189 @@
// +build !confonly
package encoding
import (
"io"
"github.com/golang/protobuf/proto"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/proxy/vless"
)
func EncodeHeaderAddons(buffer *buf.Buffer, addons *Addons) error {
switch addons.Flow {
case vless.XRO, vless.XRD:
bytes, err := proto.Marshal(addons)
if err != nil {
return newError("failed to marshal addons protobuf value").Base(err)
}
if err := buffer.WriteByte(byte(len(bytes))); err != nil {
return newError("failed to write addons protobuf length").Base(err)
}
if _, err := buffer.Write(bytes); err != nil {
return newError("failed to write addons protobuf value").Base(err)
}
default:
if err := buffer.WriteByte(0); err != nil {
return newError("failed to write addons protobuf length").Base(err)
}
}
return nil
}
func DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) {
addons := new(Addons)
buffer.Clear()
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
return nil, newError("failed to read addons protobuf length").Base(err)
}
if length := int32(buffer.Byte(0)); length != 0 {
buffer.Clear()
if _, err := buffer.ReadFullFrom(reader, length); err != nil {
return nil, newError("failed to read addons protobuf value").Base(err)
}
if err := proto.Unmarshal(buffer.Bytes(), addons); err != nil {
return nil, newError("failed to unmarshal addons protobuf value").Base(err)
}
// Verification.
switch addons.Flow {
default:
}
}
return addons, nil
}
// EncodeBodyAddons returns a Writer that auto-encrypt content written by caller.
func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, addons *Addons) buf.Writer {
switch addons.Flow {
default:
if request.Command == protocol.RequestCommandUDP {
return NewMultiLengthPacketWriter(writer.(buf.Writer))
}
}
return buf.NewWriter(writer)
}
// DecodeBodyAddons returns a Reader from which caller can fetch decrypted body.
func DecodeBodyAddons(reader io.Reader, request *protocol.RequestHeader, addons *Addons) buf.Reader {
switch addons.Flow {
default:
if request.Command == protocol.RequestCommandUDP {
return NewLengthPacketReader(reader)
}
}
return buf.NewReader(reader)
}
func NewMultiLengthPacketWriter(writer buf.Writer) *MultiLengthPacketWriter {
return &MultiLengthPacketWriter{
Writer: writer,
}
}
type MultiLengthPacketWriter struct {
buf.Writer
}
func (w *MultiLengthPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
defer buf.ReleaseMulti(mb)
mb2Write := make(buf.MultiBuffer, 0, len(mb)+1)
for _, b := range mb {
length := b.Len()
if length == 0 || length+2 > buf.Size {
continue
}
eb := buf.New()
if err := eb.WriteByte(byte(length >> 8)); err != nil {
eb.Release()
continue
}
if err := eb.WriteByte(byte(length)); err != nil {
eb.Release()
continue
}
if _, err := eb.Write(b.Bytes()); err != nil {
eb.Release()
continue
}
mb2Write = append(mb2Write, eb)
}
if mb2Write.IsEmpty() {
return nil
}
return w.Writer.WriteMultiBuffer(mb2Write)
}
func NewLengthPacketWriter(writer io.Writer) *LengthPacketWriter {
return &LengthPacketWriter{
Writer: writer,
cache: make([]byte, 0, 65536),
}
}
type LengthPacketWriter struct {
io.Writer
cache []byte
}
func (w *LengthPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
length := mb.Len() // none of mb is nil
// fmt.Println("Write", length)
if length == 0 {
return nil
}
defer func() {
w.cache = w.cache[:0]
}()
w.cache = append(w.cache, byte(length>>8), byte(length))
for i, b := range mb {
w.cache = append(w.cache, b.Bytes()...)
b.Release()
mb[i] = nil
}
if _, err := w.Write(w.cache); err != nil {
return newError("failed to write a packet").Base(err)
}
return nil
}
func NewLengthPacketReader(reader io.Reader) *LengthPacketReader {
return &LengthPacketReader{
Reader: reader,
cache: make([]byte, 2),
}
}
type LengthPacketReader struct {
io.Reader
cache []byte
}
func (r *LengthPacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if _, err := io.ReadFull(r.Reader, r.cache); err != nil { // maybe EOF
return nil, newError("failed to read packet length").Base(err)
}
length := int32(r.cache[0])<<8 | int32(r.cache[1])
// fmt.Println("Read", length)
mb := make(buf.MultiBuffer, 0, length/buf.Size+1)
for length > 0 {
size := length
if size > buf.Size {
size = buf.Size
}
length -= size
b := buf.New()
if _, err := b.ReadFullFrom(r.Reader, size); err != nil {
return nil, newError("failed to read packet payload").Base(err)
}
mb = append(mb, b)
}
return mb, nil
}

View file

@ -0,0 +1,384 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: proxy/vless/encoding/addons.proto
package encoding
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Addons struct {
Flow string `protobuf:"bytes,1,opt,name=Flow,proto3" json:"Flow,omitempty"`
Seed []byte `protobuf:"bytes,2,opt,name=Seed,proto3" json:"Seed,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Addons) Reset() { *m = Addons{} }
func (m *Addons) String() string { return proto.CompactTextString(m) }
func (*Addons) ProtoMessage() {}
func (*Addons) Descriptor() ([]byte, []int) {
return fileDescriptor_75ab671b0ca8b1cc, []int{0}
}
func (m *Addons) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Addons) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Addons.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Addons) XXX_Merge(src proto.Message) {
xxx_messageInfo_Addons.Merge(m, src)
}
func (m *Addons) XXX_Size() int {
return m.Size()
}
func (m *Addons) XXX_DiscardUnknown() {
xxx_messageInfo_Addons.DiscardUnknown(m)
}
var xxx_messageInfo_Addons proto.InternalMessageInfo
func (m *Addons) GetFlow() string {
if m != nil {
return m.Flow
}
return ""
}
func (m *Addons) GetSeed() []byte {
if m != nil {
return m.Seed
}
return nil
}
func init() {
proto.RegisterType((*Addons)(nil), "xray.proxy.vless.encoding.Addons")
}
func init() { proto.RegisterFile("proxy/vless/encoding/addons.proto", fileDescriptor_75ab671b0ca8b1cc) }
var fileDescriptor_75ab671b0ca8b1cc = []byte{
// 195 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2c, 0x28, 0xca, 0xaf,
0xa8, 0xd4, 0x2f, 0xcb, 0x49, 0x2d, 0x2e, 0xd6, 0x4f, 0xcd, 0x4b, 0xce, 0x4f, 0xc9, 0xcc, 0x4b,
0xd7, 0x4f, 0x4c, 0x49, 0xc9, 0xcf, 0x2b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0xac,
0x28, 0x4a, 0xac, 0xd4, 0x03, 0xab, 0xd3, 0x03, 0xab, 0xd3, 0x83, 0xa9, 0x53, 0x32, 0xe0, 0x62,
0x73, 0x04, 0x2b, 0x15, 0x12, 0xe2, 0x62, 0x71, 0xcb, 0xc9, 0x2f, 0x97, 0x60, 0x54, 0x60, 0xd4,
0xe0, 0x0c, 0x02, 0xb3, 0x41, 0x62, 0xc1, 0xa9, 0xa9, 0x29, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c,
0x41, 0x60, 0xb6, 0x53, 0x03, 0xe3, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78,
0x24, 0xc7, 0x38, 0xe3, 0xb1, 0x1c, 0x03, 0x97, 0x6c, 0x72, 0x7e, 0xae, 0x1e, 0x4e, 0x3b, 0x02,
0x18, 0xa3, 0x0c, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x2b, 0x4a,
0x72, 0x8a, 0xf5, 0x41, 0x8a, 0x75, 0x93, 0xf3, 0x8b, 0x52, 0xf5, 0xcb, 0x0c, 0xf5, 0xb1, 0x79,
0x60, 0x15, 0x93, 0x64, 0x04, 0xc8, 0xc0, 0x00, 0xb0, 0x81, 0x61, 0x60, 0x03, 0x5d, 0xa1, 0x72,
0x49, 0x6c, 0x60, 0x6f, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xda, 0x20, 0x32, 0x3e, 0xfb,
0x00, 0x00, 0x00,
}
func (m *Addons) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Addons) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Addons) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.XXX_unrecognized != nil {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.Seed) > 0 {
i -= len(m.Seed)
copy(dAtA[i:], m.Seed)
i = encodeVarintAddons(dAtA, i, uint64(len(m.Seed)))
i--
dAtA[i] = 0x12
}
if len(m.Flow) > 0 {
i -= len(m.Flow)
copy(dAtA[i:], m.Flow)
i = encodeVarintAddons(dAtA, i, uint64(len(m.Flow)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintAddons(dAtA []byte, offset int, v uint64) int {
offset -= sovAddons(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *Addons) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Flow)
if l > 0 {
n += 1 + l + sovAddons(uint64(l))
}
l = len(m.Seed)
if l > 0 {
n += 1 + l + sovAddons(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func sovAddons(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozAddons(x uint64) (n int) {
return sovAddons(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Addons) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowAddons
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Addons: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Addons: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Flow", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowAddons
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthAddons
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthAddons
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Flow = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Seed", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowAddons
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthAddons
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthAddons
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Seed = append(m.Seed[:0], dAtA[iNdEx:postIndex]...)
if m.Seed == nil {
m.Seed = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipAddons(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthAddons
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthAddons
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipAddons(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowAddons
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowAddons
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowAddons
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthAddons
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupAddons
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthAddons
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthAddons = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowAddons = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupAddons = fmt.Errorf("proto: unexpected end of group")
)

View file

@ -0,0 +1,12 @@
syntax = "proto3";
package xray.proxy.vless.encoding;
option csharp_namespace = "Xray.Proxy.Vless.Encoding";
option go_package = "github.com/xtls/xray-core/v1/proxy/vless/encoding";
option java_package = "com.xray.proxy.vless.encoding";
option java_multiple_files = true;
message Addons {
string Flow = 1;
bytes Seed = 2;
}

View file

@ -0,0 +1,208 @@
// +build !confonly
package encoding
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
import (
"fmt"
"io"
"syscall"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/errors"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/common/signal"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/proxy/vless"
"github.com/xtls/xray-core/v1/transport/internet/xtls"
)
const (
Version = byte(0)
)
var addrParser = protocol.NewAddressParser(
protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),
protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),
protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6),
protocol.PortThenAddress(),
)
// EncodeRequestHeader writes encoded request header into the given writer.
func EncodeRequestHeader(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons) error {
buffer := buf.StackNew()
defer buffer.Release()
if err := buffer.WriteByte(request.Version); err != nil {
return newError("failed to write request version").Base(err)
}
if _, err := buffer.Write(request.User.Account.(*vless.MemoryAccount).ID.Bytes()); err != nil {
return newError("failed to write request user id").Base(err)
}
if err := EncodeHeaderAddons(&buffer, requestAddons); err != nil {
return newError("failed to encode request header addons").Base(err)
}
if err := buffer.WriteByte(byte(request.Command)); err != nil {
return newError("failed to write request command").Base(err)
}
if request.Command != protocol.RequestCommandMux {
if err := addrParser.WriteAddressPort(&buffer, request.Address, request.Port); err != nil {
return newError("failed to write request address and port").Base(err)
}
}
if _, err := writer.Write(buffer.Bytes()); err != nil {
return newError("failed to write request header").Base(err)
}
return nil
}
// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.
func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validator *vless.Validator) (*protocol.RequestHeader, *Addons, bool, error) {
buffer := buf.StackNew()
defer buffer.Release()
request := new(protocol.RequestHeader)
if isfb {
request.Version = first.Byte(0)
} else {
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
return nil, nil, false, newError("failed to read request version").Base(err)
}
request.Version = buffer.Byte(0)
}
switch request.Version {
case 0:
var id [16]byte
if isfb {
copy(id[:], first.BytesRange(1, 17))
} else {
buffer.Clear()
if _, err := buffer.ReadFullFrom(reader, 16); err != nil {
return nil, nil, false, newError("failed to read request user id").Base(err)
}
copy(id[:], buffer.Bytes())
}
if request.User = validator.Get(id); request.User == nil {
return nil, nil, isfb, newError("invalid request user id")
}
if isfb {
first.Advance(17)
}
requestAddons, err := DecodeHeaderAddons(&buffer, reader)
if err != nil {
return nil, nil, false, newError("failed to decode request header addons").Base(err)
}
buffer.Clear()
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
return nil, nil, false, newError("failed to read request command").Base(err)
}
request.Command = protocol.RequestCommand(buffer.Byte(0))
switch request.Command {
case protocol.RequestCommandMux:
request.Address = net.DomainAddress("v1.mux.cool")
request.Port = 0
case protocol.RequestCommandTCP, protocol.RequestCommandUDP:
if addr, port, err := addrParser.ReadAddressPort(&buffer, reader); err == nil {
request.Address = addr
request.Port = port
}
}
if request.Address == nil {
return nil, nil, false, newError("invalid request address")
}
return request, requestAddons, false, nil
default:
return nil, nil, isfb, newError("invalid request version")
}
}
// EncodeResponseHeader writes encoded response header into the given writer.
func EncodeResponseHeader(writer io.Writer, request *protocol.RequestHeader, responseAddons *Addons) error {
buffer := buf.StackNew()
defer buffer.Release()
if err := buffer.WriteByte(request.Version); err != nil {
return newError("failed to write response version").Base(err)
}
if err := EncodeHeaderAddons(&buffer, responseAddons); err != nil {
return newError("failed to encode response header addons").Base(err)
}
if _, err := writer.Write(buffer.Bytes()); err != nil {
return newError("failed to write response header").Base(err)
}
return nil
}
// DecodeResponseHeader decodes and returns (if successful) a ResponseHeader from an input stream.
func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*Addons, error) {
buffer := buf.StackNew()
defer buffer.Release()
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
return nil, newError("failed to read response version").Base(err)
}
if buffer.Byte(0) != request.Version {
return nil, newError("unexpected response version. Expecting ", int(request.Version), " but actually ", int(buffer.Byte(0)))
}
responseAddons, err := DecodeHeaderAddons(&buffer, reader)
if err != nil {
return nil, newError("failed to decode response header addons").Base(err)
}
return responseAddons, nil
}
func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *xtls.Conn, rawConn syscall.RawConn, counter stats.Counter) error {
err := func() error {
var ct stats.Counter
for {
if conn.DirectIn {
conn.DirectIn = false
reader = buf.NewReadVReader(conn.Connection, rawConn)
ct = counter
if conn.SHOW {
fmt.Println(conn.MARK, "ReadV")
}
}
buffer, err := reader.ReadMultiBuffer()
if !buffer.IsEmpty() {
if ct != nil {
ct.Add(int64(buffer.Len()))
}
timer.Update()
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr
}
}
if err != nil {
return err
}
}
}()
if err != nil && errors.Cause(err) != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,126 @@
package encoding_test
import (
"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/common/uuid"
"github.com/xtls/xray-core/v1/proxy/vless"
. "github.com/xtls/xray-core/v1/proxy/vless/encoding"
)
func toAccount(a *vless.Account) protocol.Account {
account, err := a.AsAccount()
common.Must(err)
return account
}
func TestRequestSerialization(t *testing.T) {
user := &protocol.MemoryUser{
Level: 0,
Email: "test@example.com",
}
id := uuid.New()
account := &vless.Account{
Id: id.String(),
}
user.Account = toAccount(account)
expectedRequest := &protocol.RequestHeader{
Version: Version,
User: user,
Command: protocol.RequestCommandTCP,
Address: net.DomainAddress("www.example.com"),
Port: net.Port(443),
}
expectedAddons := &Addons{}
buffer := buf.StackNew()
common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons))
Validator := new(vless.Validator)
Validator.Add(user)
actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
common.Must(err)
if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" {
t.Error(r)
}
if r := cmp.Diff(actualAddons, expectedAddons); r != "" {
t.Error(r)
}
}
func TestInvalidRequest(t *testing.T) {
user := &protocol.MemoryUser{
Level: 0,
Email: "test@example.com",
}
id := uuid.New()
account := &vless.Account{
Id: id.String(),
}
user.Account = toAccount(account)
expectedRequest := &protocol.RequestHeader{
Version: Version,
User: user,
Command: protocol.RequestCommand(100),
Address: net.DomainAddress("www.example.com"),
Port: net.Port(443),
}
expectedAddons := &Addons{}
buffer := buf.StackNew()
common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons))
Validator := new(vless.Validator)
Validator.Add(user)
_, _, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
if err == nil {
t.Error("nil error")
}
}
func TestMuxRequest(t *testing.T) {
user := &protocol.MemoryUser{
Level: 0,
Email: "test@example.com",
}
id := uuid.New()
account := &vless.Account{
Id: id.String(),
}
user.Account = toAccount(account)
expectedRequest := &protocol.RequestHeader{
Version: Version,
User: user,
Command: protocol.RequestCommandMux,
Address: net.DomainAddress("v1.mux.cool"),
}
expectedAddons := &Addons{}
buffer := buf.StackNew()
common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons))
Validator := new(vless.Validator)
Validator.Add(user)
actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
common.Must(err)
if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" {
t.Error(r)
}
if r := cmp.Diff(actualAddons, expectedAddons); r != "" {
t.Error(r)
}
}

View file

@ -0,0 +1,9 @@
package encoding
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View file

@ -0,0 +1,9 @@
package vless
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View file

@ -0,0 +1,3 @@
// +build !confonly
package inbound

View file

@ -0,0 +1,286 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/vless/inbound/config.proto
package inbound
import (
proto "github.com/golang/protobuf/proto"
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
type Fallback struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Alpn string `protobuf:"bytes,1,opt,name=alpn,proto3" json:"alpn,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
Dest string `protobuf:"bytes,4,opt,name=dest,proto3" json:"dest,omitempty"`
Xver uint64 `protobuf:"varint,5,opt,name=xver,proto3" json:"xver,omitempty"`
}
func (x *Fallback) Reset() {
*x = Fallback{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_vless_inbound_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Fallback) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Fallback) ProtoMessage() {}
func (x *Fallback) ProtoReflect() protoreflect.Message {
mi := &file_proxy_vless_inbound_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 Fallback.ProtoReflect.Descriptor instead.
func (*Fallback) Descriptor() ([]byte, []int) {
return file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{0}
}
func (x *Fallback) GetAlpn() string {
if x != nil {
return x.Alpn
}
return ""
}
func (x *Fallback) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *Fallback) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Fallback) GetDest() string {
if x != nil {
return x.Dest
}
return ""
}
func (x *Fallback) GetXver() uint64 {
if x != nil {
return x.Xver
}
return 0
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
// Decryption settings. Only applies to server side, and only accepts "none"
// for now.
Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_vless_inbound_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_vless_inbound_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 Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{1}
}
func (x *Config) GetClients() []*protocol.User {
if x != nil {
return x.Clients
}
return nil
}
func (x *Config) GetDecryption() string {
if x != nil {
return x.Decryption
}
return ""
}
func (x *Config) GetFallbacks() []*Fallback {
if x != nil {
return x.Fallbacks
}
return nil
}
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x0a, 0x20, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x18, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x1a, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6e, 0x0a, 0x08, 0x46, 0x61, 0x6c, 0x6c,
0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01,
0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72,
0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63,
0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64,
0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x09, 0x66, 0x61, 0x6c,
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e,
0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x42, 0x6d, 0x0a, 0x1c, 0x63,
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c,
0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x30, 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, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa,
0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65,
0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_proxy_vless_inbound_config_proto_rawDescOnce sync.Once
file_proxy_vless_inbound_config_proto_rawDescData = file_proxy_vless_inbound_config_proto_rawDesc
)
func file_proxy_vless_inbound_config_proto_rawDescGZIP() []byte {
file_proxy_vless_inbound_config_proto_rawDescOnce.Do(func() {
file_proxy_vless_inbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vless_inbound_config_proto_rawDescData)
})
return file_proxy_vless_inbound_config_proto_rawDescData
}
var file_proxy_vless_inbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_vless_inbound_config_proto_goTypes = []interface{}{
(*Fallback)(nil), // 0: xray.proxy.vless.inbound.Fallback
(*Config)(nil), // 1: xray.proxy.vless.inbound.Config
(*protocol.User)(nil), // 2: xray.common.protocol.User
}
var file_proxy_vless_inbound_config_proto_depIdxs = []int32{
2, // 0: xray.proxy.vless.inbound.Config.clients:type_name -> xray.common.protocol.User
0, // 1: xray.proxy.vless.inbound.Config.fallbacks:type_name -> xray.proxy.vless.inbound.Fallback
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
}
func init() { file_proxy_vless_inbound_config_proto_init() }
func file_proxy_vless_inbound_config_proto_init() {
if File_proxy_vless_inbound_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_vless_inbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Fallback); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_vless_inbound_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); 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_vless_inbound_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_vless_inbound_config_proto_goTypes,
DependencyIndexes: file_proxy_vless_inbound_config_proto_depIdxs,
MessageInfos: file_proxy_vless_inbound_config_proto_msgTypes,
}.Build()
File_proxy_vless_inbound_config_proto = out.File
file_proxy_vless_inbound_config_proto_rawDesc = nil
file_proxy_vless_inbound_config_proto_goTypes = nil
file_proxy_vless_inbound_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,25 @@
syntax = "proto3";
package xray.proxy.vless.inbound;
option csharp_namespace = "Xray.Proxy.Vless.Inbound";
option go_package = "github.com/xtls/xray-core/v1/proxy/vless/inbound";
option java_package = "com.xray.proxy.vless.inbound";
option java_multiple_files = true;
import "common/protocol/user.proto";
message Fallback {
string alpn = 1;
string path = 2;
string type = 3;
string dest = 4;
uint64 xver = 5;
}
message Config {
repeated xray.common.protocol.User clients = 1;
// Decryption settings. Only applies to server side, and only accepts "none"
// for now.
string decryption = 2;
repeated Fallback fallbacks = 3;
}

View file

@ -0,0 +1,9 @@
package inbound
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View file

@ -0,0 +1,506 @@
// +build !confonly
package inbound
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
import (
"context"
"io"
"strconv"
"syscall"
"time"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/common/errors"
"github.com/xtls/xray-core/v1/common/log"
"github.com/xtls/xray-core/v1/common/net"
"github.com/xtls/xray-core/v1/common/platform"
"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"
core "github.com/xtls/xray-core/v1/core"
"github.com/xtls/xray-core/v1/features/dns"
feature_inbound "github.com/xtls/xray-core/v1/features/inbound"
"github.com/xtls/xray-core/v1/features/policy"
"github.com/xtls/xray-core/v1/features/routing"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/proxy/vless"
"github.com/xtls/xray-core/v1/proxy/vless/encoding"
"github.com/xtls/xray-core/v1/transport/internet"
"github.com/xtls/xray-core/v1/transport/internet/tls"
"github.com/xtls/xray-core/v1/transport/internet/xtls"
)
var (
xtls_show = false
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
var dc dns.Client
if err := core.RequireFeatures(ctx, func(d dns.Client) error {
dc = d
return nil
}); err != nil {
return nil, err
}
return New(ctx, config.(*Config), dc)
}))
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
xtlsShow := platform.NewEnvFlag("xray.vless.xtls.show").GetValue(func() string { return defaultFlagValue })
if xtlsShow == "true" {
xtls_show = true
}
}
// Handler is an inbound connection handler that handles messages in VLess protocol.
type Handler struct {
inboundHandlerManager feature_inbound.Manager
policyManager policy.Manager
validator *vless.Validator
dns dns.Client
fallbacks map[string]map[string]*Fallback // or nil
// regexps map[string]*regexp.Regexp // or nil
}
// New creates a new VLess inbound handler.
func New(ctx context.Context, config *Config, dc dns.Client) (*Handler, error) {
v := core.MustFromContext(ctx)
handler := &Handler{
inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
validator: new(vless.Validator),
dns: dc,
}
for _, user := range config.Clients {
u, err := user.ToMemoryUser()
if err != nil {
return nil, newError("failed to get VLESS user").Base(err).AtError()
}
if err := handler.AddUser(ctx, u); err != nil {
return nil, newError("failed to initiate user").Base(err).AtError()
}
}
if config.Fallbacks != nil {
handler.fallbacks = make(map[string]map[string]*Fallback)
// handler.regexps = make(map[string]*regexp.Regexp)
for _, fb := range config.Fallbacks {
if handler.fallbacks[fb.Alpn] == nil {
handler.fallbacks[fb.Alpn] = make(map[string]*Fallback)
}
handler.fallbacks[fb.Alpn][fb.Path] = fb
/*
if fb.Path != "" {
if r, err := regexp.Compile(fb.Path); err != nil {
return nil, newError("invalid path regexp").Base(err).AtError()
} else {
handler.regexps[fb.Path] = r
}
}
*/
}
if handler.fallbacks[""] != nil {
for alpn, pfb := range handler.fallbacks {
if alpn != "" { // && alpn != "h2" {
for path, fb := range handler.fallbacks[""] {
if pfb[path] == nil {
pfb[path] = fb
}
}
}
}
}
}
return handler, nil
}
// Close implements common.Closable.Close().
func (h *Handler) Close() error {
return errors.Combine(common.Close(h.validator))
}
// AddUser implements proxy.UserManager.AddUser().
func (h *Handler) AddUser(ctx context.Context, u *protocol.MemoryUser) error {
return h.validator.Add(u)
}
// RemoveUser implements proxy.UserManager.RemoveUser().
func (h *Handler) RemoveUser(ctx context.Context, e string) error {
return h.validator.Del(e)
}
// Network implements proxy.Inbound.Network().
func (*Handler) Network() []net.Network {
return []net.Network{net.Network_TCP, net.Network_UNIX}
}
// Process implements proxy.Inbound.Process().
func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher routing.Dispatcher) error {
sid := session.ExportIDToError(ctx)
iConn := connection
statConn, ok := iConn.(*internet.StatCouterConnection)
if ok {
iConn = statConn.Connection
}
sessionPolicy := h.policyManager.ForLevel(0)
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return newError("unable to set read deadline").Base(err).AtWarning()
}
first := buf.New()
defer first.Release()
firstLen, _ := first.ReadFrom(connection)
newError("firstLen = ", firstLen).AtInfo().WriteToLog(sid)
reader := &buf.BufferedReader{
Reader: buf.NewReader(connection),
Buffer: buf.MultiBuffer{first},
}
var request *protocol.RequestHeader
var requestAddons *encoding.Addons
var err error
apfb := h.fallbacks
isfb := apfb != nil
if isfb && firstLen < 18 {
err = newError("fallback directly")
} else {
request, requestAddons, isfb, err = encoding.DecodeRequestHeader(isfb, first, reader, h.validator)
}
if err != nil {
if isfb {
if err := connection.SetReadDeadline(time.Time{}); err != nil {
newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid)
}
newError("fallback starts").Base(err).AtInfo().WriteToLog(sid)
alpn := ""
if len(apfb) > 1 || apfb[""] == nil {
if tlsConn, ok := iConn.(*tls.Conn); ok {
alpn = tlsConn.ConnectionState().NegotiatedProtocol
newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid)
} else if xtlsConn, ok := iConn.(*xtls.Conn); ok {
alpn = xtlsConn.ConnectionState().NegotiatedProtocol
newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid)
}
if apfb[alpn] == nil {
alpn = ""
}
}
pfb := apfb[alpn]
if pfb == nil {
return newError(`failed to find the default "alpn" config`).AtWarning()
}
path := ""
if len(pfb) > 1 || pfb[""] == nil {
/*
if lines := bytes.Split(firstBytes, []byte{'\r', '\n'}); len(lines) > 1 {
if s := bytes.Split(lines[0], []byte{' '}); len(s) == 3 {
if len(s[0]) < 8 && len(s[1]) > 0 && len(s[2]) == 8 {
newError("realPath = " + string(s[1])).AtInfo().WriteToLog(sid)
for _, fb := range pfb {
if fb.Path != "" && h.regexps[fb.Path].Match(s[1]) {
path = fb.Path
break
}
}
}
}
}
*/
if firstLen >= 18 && first.Byte(4) != '*' { // not h2c
firstBytes := first.Bytes()
for i := 4; i <= 8; i++ { // 5 -> 9
if firstBytes[i] == '/' && firstBytes[i-1] == ' ' {
search := len(firstBytes)
if search > 64 {
search = 64 // up to about 60
}
for j := i + 1; j < search; j++ {
k := firstBytes[j]
if k == '\r' || k == '\n' { // avoid logging \r or \n
break
}
if k == ' ' {
path = string(firstBytes[i:j])
newError("realPath = " + path).AtInfo().WriteToLog(sid)
if pfb[path] == nil {
path = ""
}
break
}
}
break
}
}
}
}
fb := pfb[path]
if fb == nil {
return newError(`failed to find the default "path" config`).AtWarning()
}
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
var conn net.Conn
if err := retry.ExponentialBackoff(5, 100).On(func() error {
var dialer net.Dialer
conn, err = dialer.DialContext(ctx, fb.Type, fb.Dest)
if err != nil {
return err
}
return nil
}); err != nil {
return newError("failed to dial to " + fb.Dest).Base(err).AtWarning()
}
defer conn.Close()
serverReader := buf.NewReader(conn)
serverWriter := buf.NewWriter(conn)
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
if fb.Xver != 0 {
remoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String())
if err != nil {
return err
}
localAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String())
if err != nil {
return err
}
ipv4 := true
for i := 0; i < len(remoteAddr); i++ {
if remoteAddr[i] == ':' {
ipv4 = false
break
}
}
pro := buf.New()
defer pro.Release()
switch fb.Xver {
case 1:
if ipv4 {
pro.Write([]byte("PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"))
} else {
pro.Write([]byte("PROXY TCP6 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"))
}
case 2:
pro.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21")) // signature + v2 + PROXY
if ipv4 {
pro.Write([]byte("\x11\x00\x0C")) // AF_INET + STREAM + 12 bytes
pro.Write(net.ParseIP(remoteAddr).To4())
pro.Write(net.ParseIP(localAddr).To4())
} else {
pro.Write([]byte("\x21\x00\x24")) // AF_INET6 + STREAM + 36 bytes
pro.Write(net.ParseIP(remoteAddr).To16())
pro.Write(net.ParseIP(localAddr).To16())
}
p1, _ := strconv.ParseUint(remotePort, 10, 16)
p2, _ := strconv.ParseUint(localPort, 10, 16)
pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)})
}
if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil {
return newError("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning()
}
}
if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to fallback request payload").Base(err).AtInfo()
}
return nil
}
writer := buf.NewWriter(connection)
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to deliver response payload").Base(err).AtInfo()
}
return nil
}
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil {
common.Interrupt(serverReader)
common.Interrupt(serverWriter)
return newError("fallback ends").Base(err).AtInfo()
}
return nil
}
if errors.Cause(err) != io.EOF {
log.Record(&log.AccessMessage{
From: connection.RemoteAddr(),
To: "",
Status: log.AccessRejected,
Reason: err,
})
err = newError("invalid request from ", connection.RemoteAddr()).Base(err).AtInfo()
}
return err
}
if err := connection.SetReadDeadline(time.Time{}); err != nil {
newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid)
}
newError("received request for ", request.Destination()).AtInfo().WriteToLog(sid)
inbound := session.InboundFromContext(ctx)
if inbound == nil {
panic("no inbound metadata")
}
inbound.User = request.User
account := request.User.Account.(*vless.MemoryAccount)
responseAddons := &encoding.Addons{
// Flow: requestAddons.Flow,
}
var rawConn syscall.RawConn
switch requestAddons.Flow {
case vless.XRO, vless.XRD:
if account.Flow == requestAddons.Flow {
switch request.Command {
case protocol.RequestCommandMux:
return newError(requestAddons.Flow + " doesn't support Mux").AtWarning()
case protocol.RequestCommandUDP:
return newError(requestAddons.Flow + " doesn't support UDP").AtWarning()
case protocol.RequestCommandTCP:
if xtlsConn, ok := iConn.(*xtls.Conn); ok {
xtlsConn.RPRX = true
xtlsConn.SHOW = xtls_show
xtlsConn.MARK = "XTLS"
if requestAddons.Flow == vless.XRD {
xtlsConn.DirectMode = true
if sc, ok := xtlsConn.Connection.(syscall.Conn); ok {
rawConn, _ = sc.SyscallConn()
}
}
} else {
return newError(`failed to use ` + requestAddons.Flow + `, maybe "security" is not "xtls"`).AtWarning()
}
}
} else {
return newError(account.ID.String() + " is not able to use " + requestAddons.Flow).AtWarning()
}
case "":
default:
return newError("unknown request flow " + requestAddons.Flow).AtWarning()
}
if request.Command != protocol.RequestCommandMux {
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: connection.RemoteAddr(),
To: request.Destination(),
Status: log.AccessAccepted,
Reason: "",
Email: request.User.Email,
})
}
sessionPolicy = h.policyManager.ForLevel(request.User.Level)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
link, err := dispatcher.Dispatch(ctx, request.Destination())
if err != nil {
return newError("failed to dispatch request to ", request.Destination()).Base(err).AtWarning()
}
serverReader := link.Reader // .(*pipe.Reader)
serverWriter := link.Writer // .(*pipe.Writer)
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
// default: clientReader := reader
clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons)
var err error
if rawConn != nil {
var counter stats.Counter
if statConn != nil {
counter = statConn.ReadCounter
}
err = encoding.ReadV(clientReader, serverWriter, timer, iConn.(*xtls.Conn), rawConn, counter)
} else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
}
if err != nil {
return newError("failed to transfer request payload").Base(err).AtInfo()
}
return nil
}
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection))
if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil {
return newError("failed to encode response header").Base(err).AtWarning()
}
// default: clientWriter := bufferWriter
clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, responseAddons)
{
multiBuffer, err := serverReader.ReadMultiBuffer()
if err != nil {
return err // ...
}
if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil {
return err // ...
}
}
// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
if err := bufferWriter.SetBuffered(false); err != nil {
return newError("failed to write A response payload").Base(err).AtWarning()
}
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
if err := buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transfer response payload").Base(err).AtInfo()
}
// Indicates the end of response payload.
switch responseAddons.Flow {
default:
}
return nil
}
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), getResponse); err != nil {
common.Interrupt(serverReader)
common.Interrupt(serverWriter)
return newError("connection ends").Base(err).AtInfo()
}
return nil
}

View file

@ -0,0 +1,3 @@
// +build !confonly
package outbound

View file

@ -0,0 +1,163 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/vless/outbound/config.proto
package outbound
import (
proto "github.com/golang/protobuf/proto"
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
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Vnext []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=vnext,proto3" json:"vnext,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_vless_outbound_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_vless_outbound_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 Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_vless_outbound_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetVnext() []*protocol.ServerEndpoint {
if x != nil {
return x.Vnext
}
return nil
}
var File_proxy_vless_outbound_config_proto protoreflect.FileDescriptor
var file_proxy_vless_outbound_config_proto_rawDesc = []byte{
0x0a, 0x21, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75,
0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x19, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 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, 0x44, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x05, 0x76,
0x6e, 0x65, 0x78, 0x74, 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, 0x05, 0x76, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x70, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e,
0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x31, 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, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x19,
0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73,
0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_proxy_vless_outbound_config_proto_rawDescOnce sync.Once
file_proxy_vless_outbound_config_proto_rawDescData = file_proxy_vless_outbound_config_proto_rawDesc
)
func file_proxy_vless_outbound_config_proto_rawDescGZIP() []byte {
file_proxy_vless_outbound_config_proto_rawDescOnce.Do(func() {
file_proxy_vless_outbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vless_outbound_config_proto_rawDescData)
})
return file_proxy_vless_outbound_config_proto_rawDescData
}
var file_proxy_vless_outbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_vless_outbound_config_proto_goTypes = []interface{}{
(*Config)(nil), // 0: xray.proxy.vless.outbound.Config
(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint
}
var file_proxy_vless_outbound_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.vless.outbound.Config.vnext:type_name -> xray.common.protocol.ServerEndpoint
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_vless_outbound_config_proto_init() }
func file_proxy_vless_outbound_config_proto_init() {
if File_proxy_vless_outbound_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_vless_outbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); 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_vless_outbound_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_vless_outbound_config_proto_goTypes,
DependencyIndexes: file_proxy_vless_outbound_config_proto_depIdxs,
MessageInfos: file_proxy_vless_outbound_config_proto_msgTypes,
}.Build()
File_proxy_vless_outbound_config_proto = out.File
file_proxy_vless_outbound_config_proto_rawDesc = nil
file_proxy_vless_outbound_config_proto_goTypes = nil
file_proxy_vless_outbound_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,13 @@
syntax = "proto3";
package xray.proxy.vless.outbound;
option csharp_namespace = "Xray.Proxy.Vless.Outbound";
option go_package = "github.com/xtls/xray-core/v1/proxy/vless/outbound";
option java_package = "com.xray.proxy.vless.outbound";
option java_multiple_files = true;
import "common/protocol/server_spec.proto";
message Config {
repeated xray.common.protocol.ServerEndpoint vnext = 1;
}

View file

@ -0,0 +1,9 @@
package outbound
import "github.com/xtls/xray-core/v1/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View file

@ -0,0 +1,240 @@
// +build !confonly
package outbound
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
import (
"context"
"syscall"
"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/platform"
"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"
core "github.com/xtls/xray-core/v1/core"
"github.com/xtls/xray-core/v1/features/policy"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/proxy/vless"
"github.com/xtls/xray-core/v1/proxy/vless/encoding"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/internet"
"github.com/xtls/xray-core/v1/transport/internet/xtls"
)
var (
xtls_show = false
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config))
}))
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
xtlsShow := platform.NewEnvFlag("xray.vless.xtls.show").GetValue(func() string { return defaultFlagValue })
if xtlsShow == "true" {
xtls_show = true
}
}
// Handler is an outbound connection handler for VLess protocol.
type Handler struct {
serverList *protocol.ServerList
serverPicker protocol.ServerPicker
policyManager policy.Manager
}
// New creates a new VLess outbound handler.
func New(ctx context.Context, config *Config) (*Handler, error) {
serverList := protocol.NewServerList()
for _, rec := range config.Vnext {
s, err := protocol.NewServerSpecFromPB(rec)
if err != nil {
return nil, newError("failed to parse server spec").Base(err).AtError()
}
serverList.AddServer(s)
}
v := core.MustFromContext(ctx)
handler := &Handler{
serverList: serverList,
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
return handler, nil
}
// Process implements proxy.Outbound.Process().
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
var rec *protocol.ServerSpec
var conn internet.Connection
if err := retry.ExponentialBackoff(5, 200).On(func() error {
rec = h.serverPicker.PickServer()
var err error
conn, err = dialer.Dial(ctx, rec.Destination())
if err != nil {
return err
}
return nil
}); err != nil {
return newError("failed to find an available destination").Base(err).AtWarning()
}
defer conn.Close()
iConn := conn
statConn, ok := iConn.(*internet.StatCouterConnection)
if ok {
iConn = statConn.Connection
}
outbound := session.OutboundFromContext(ctx)
if outbound == nil || !outbound.Target.IsValid() {
return newError("target not specified").AtError()
}
target := outbound.Target
newError("tunneling request to ", target, " via ", rec.Destination()).AtInfo().WriteToLog(session.ExportIDToError(ctx))
command := protocol.RequestCommandTCP
if target.Network == net.Network_UDP {
command = protocol.RequestCommandUDP
}
if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" {
command = protocol.RequestCommandMux
}
request := &protocol.RequestHeader{
Version: encoding.Version,
User: rec.PickUser(),
Command: command,
Address: target.Address,
Port: target.Port,
}
account := request.User.Account.(*vless.MemoryAccount)
requestAddons := &encoding.Addons{
Flow: account.Flow,
}
var rawConn syscall.RawConn
allowUDP443 := false
switch requestAddons.Flow {
case vless.XRO + "-udp443", vless.XRD + "-udp443":
allowUDP443 = true
requestAddons.Flow = requestAddons.Flow[:16]
fallthrough
case vless.XRO, vless.XRD:
switch request.Command {
case protocol.RequestCommandMux:
return newError(requestAddons.Flow + " doesn't support Mux").AtWarning()
case protocol.RequestCommandUDP:
if !allowUDP443 && request.Port == 443 {
return newError(requestAddons.Flow + " stopped UDP/443").AtInfo()
}
requestAddons.Flow = ""
case protocol.RequestCommandTCP:
if xtlsConn, ok := iConn.(*xtls.Conn); ok {
xtlsConn.RPRX = true
xtlsConn.SHOW = xtls_show
xtlsConn.MARK = "XTLS"
if requestAddons.Flow == vless.XRD {
xtlsConn.DirectMode = true
if sc, ok := xtlsConn.Connection.(syscall.Conn); ok {
rawConn, _ = sc.SyscallConn()
}
}
} else {
return newError(`failed to use ` + requestAddons.Flow + `, maybe "security" is not "xtls"`).AtWarning()
}
}
default:
if _, ok := iConn.(*xtls.Conn); ok {
panic(`To avoid misunderstanding, you must fill in VLESS "flow" when using XTLS.`)
}
}
sessionPolicy := h.policyManager.ForLevel(request.User.Level)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
clientReader := link.Reader // .(*pipe.Reader)
clientWriter := link.Writer // .(*pipe.Writer)
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
if err := encoding.EncodeRequestHeader(bufferWriter, request, requestAddons); err != nil {
return newError("failed to encode request header").Base(err).AtWarning()
}
// default: serverWriter := bufferWriter
serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons)
if err := buf.CopyOnceTimeout(clientReader, serverWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
return err // ...
}
// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
if err := bufferWriter.SetBuffered(false); err != nil {
return newError("failed to write A request payload").Base(err).AtWarning()
}
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
if err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transfer request payload").Base(err).AtInfo()
}
// Indicates the end of request payload.
switch requestAddons.Flow {
default:
}
return nil
}
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
responseAddons, err := encoding.DecodeResponseHeader(conn, request)
if err != nil {
return newError("failed to decode response header").Base(err).AtInfo()
}
// default: serverReader := buf.NewReader(conn)
serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons)
if rawConn != nil {
var counter stats.Counter
if statConn != nil {
counter = statConn.ReadCounter
}
err = encoding.ReadV(serverReader, clientWriter, timer, iConn.(*xtls.Conn), rawConn, counter)
} else {
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
}
if err != nil {
return newError("failed to transfer response payload").Base(err).AtInfo()
}
return nil
}
if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil {
return newError("connection ends").Base(err).AtInfo()
}
return nil
}

54
proxy/vless/validator.go Normal file
View file

@ -0,0 +1,54 @@
// +build !confonly
package vless
import (
"strings"
"sync"
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/common/uuid"
)
// Validator stores valid VLESS users.
type Validator struct {
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
email sync.Map
users sync.Map
}
// Add a VLESS user, Email must be empty or unique.
func (v *Validator) Add(u *protocol.MemoryUser) error {
if u.Email != "" {
_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)
if loaded {
return newError("User ", u.Email, " already exists.")
}
}
v.users.Store(u.Account.(*MemoryAccount).ID.UUID(), u)
return nil
}
// Del a VLESS user with a non-empty Email.
func (v *Validator) Del(e string) error {
if e == "" {
return newError("Email must not be empty.")
}
le := strings.ToLower(e)
u, _ := v.email.Load(le)
if u == nil {
return newError("User ", e, " not found.")
}
v.email.Delete(le)
v.users.Delete(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID())
return nil
}
// Get a VLESS user with UUID, nil if user doesn't exist.
func (v *Validator) Get(id uuid.UUID) *protocol.MemoryUser {
u, _ := v.users.Load(id)
if u != nil {
return u.(*protocol.MemoryUser)
}
return nil
}

13
proxy/vless/vless.go Normal file
View file

@ -0,0 +1,13 @@
// Package vless contains the implementation of VLess protocol and transportation.
//
// VLess contains both inbound and outbound connections. VLess inbound is usually used on servers
// together with 'freedom' to talk to final destination, while VLess outbound is usually used on
// clients with 'socks' for proxying.
package vless
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
const (
XRO = "xtls-rprx-origin"
XRD = "xtls-rprx-direct"
)

51
proxy/vmess/account.go Normal file
View file

@ -0,0 +1,51 @@
// +build !confonly
package vmess
import (
"github.com/xtls/xray-core/v1/common/dice"
"github.com/xtls/xray-core/v1/common/protocol"
"github.com/xtls/xray-core/v1/common/uuid"
)
// MemoryAccount is an in-memory form of VMess account.
type MemoryAccount struct {
// ID is the main ID of the account.
ID *protocol.ID
// AlterIDs are the alternative IDs of the account.
AlterIDs []*protocol.ID
// Security type of the account. Used for client connections.
Security protocol.SecurityType
}
// AnyValidID returns an ID that is either the main ID or one of the alternative IDs if any.
func (a *MemoryAccount) AnyValidID() *protocol.ID {
if len(a.AlterIDs) == 0 {
return a.ID
}
return a.AlterIDs[dice.Roll(len(a.AlterIDs))]
}
// Equals implements protocol.Account.
func (a *MemoryAccount) Equals(account protocol.Account) bool {
vmessAccount, ok := account.(*MemoryAccount)
if !ok {
return false
}
// TODO: handle AlterIds difference
return a.ID.Equals(vmessAccount.ID)
}
// AsAccount implements protocol.Account.
func (a *Account) AsAccount() (protocol.Account, error) {
id, err := uuid.ParseString(a.Id)
if err != nil {
return nil, newError("failed to parse ID").Base(err).AtError()
}
protoID := protocol.NewID(id)
return &MemoryAccount{
ID: protoID,
AlterIDs: protocol.NewAlterIDs(protoID, uint16(a.AlterId)),
Security: a.SecuritySettings.GetSecurityType(),
}, nil
}

195
proxy/vmess/account.pb.go Normal file
View file

@ -0,0 +1,195 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: proxy/vmess/account.proto
package vmess
import (
proto "github.com/golang/protobuf/proto"
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
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// ID of the account, in the form of a UUID, e.g.,
// "66ad4540-b58c-4ad2-9926-ea63445a9b57".
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Number of alternative IDs. Client and server must share the same number.
AlterId uint32 `protobuf:"varint,2,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
// Security settings. Only applies to client side.
SecuritySettings *protocol.SecurityConfig `protobuf:"bytes,3,opt,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"`
// Define tests enabled for this account
TestsEnabled string `protobuf:"bytes,4,opt,name=tests_enabled,json=testsEnabled,proto3" json:"tests_enabled,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_vmess_account_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_vmess_account_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_vmess_account_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Account) GetAlterId() uint32 {
if x != nil {
return x.AlterId
}
return 0
}
func (x *Account) GetSecuritySettings() *protocol.SecurityConfig {
if x != nil {
return x.SecuritySettings
}
return nil
}
func (x *Account) GetTestsEnabled() string {
if x != nil {
return x.TestsEnabled
}
return ""
}
var File_proxy_vmess_account_proto protoreflect.FileDescriptor
var file_proxy_vmess_account_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x1a, 0x1d, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x68,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xac, 0x01, 0x0a,
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x74, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6c, 0x74, 0x65,
0x72, 0x49, 0x64, 0x12, 0x51, 0x0a, 0x11, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f,
0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 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, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x65,
0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x73, 0x5f,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74,
0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x55, 0x0a, 0x14, 0x63,
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d,
0x65, 0x73, 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, 0x76, 0x6d, 0x65, 0x73, 0x73, 0xaa,
0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65,
0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_vmess_account_proto_rawDescOnce sync.Once
file_proxy_vmess_account_proto_rawDescData = file_proxy_vmess_account_proto_rawDesc
)
func file_proxy_vmess_account_proto_rawDescGZIP() []byte {
file_proxy_vmess_account_proto_rawDescOnce.Do(func() {
file_proxy_vmess_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vmess_account_proto_rawDescData)
})
return file_proxy_vmess_account_proto_rawDescData
}
var file_proxy_vmess_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_vmess_account_proto_goTypes = []interface{}{
(*Account)(nil), // 0: xray.proxy.vmess.Account
(*protocol.SecurityConfig)(nil), // 1: xray.common.protocol.SecurityConfig
}
var file_proxy_vmess_account_proto_depIdxs = []int32{
1, // 0: xray.proxy.vmess.Account.security_settings:type_name -> xray.common.protocol.SecurityConfig
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_vmess_account_proto_init() }
func file_proxy_vmess_account_proto_init() {
if File_proxy_vmess_account_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_vmess_account_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
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proxy_vmess_account_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_vmess_account_proto_goTypes,
DependencyIndexes: file_proxy_vmess_account_proto_depIdxs,
MessageInfos: file_proxy_vmess_account_proto_msgTypes,
}.Build()
File_proxy_vmess_account_proto = out.File
file_proxy_vmess_account_proto_rawDesc = nil
file_proxy_vmess_account_proto_goTypes = nil
file_proxy_vmess_account_proto_depIdxs = nil
}

21
proxy/vmess/account.proto Normal file
View file

@ -0,0 +1,21 @@
syntax = "proto3";
package xray.proxy.vmess;
option csharp_namespace = "Xray.Proxy.Vmess";
option go_package = "github.com/xtls/xray-core/v1/proxy/vmess";
option java_package = "com.xray.proxy.vmess";
option java_multiple_files = true;
import "common/protocol/headers.proto";
message Account {
// ID of the account, in the form of a UUID, e.g.,
// "66ad4540-b58c-4ad2-9926-ea63445a9b57".
string id = 1;
// Number of alternative IDs. Client and server must share the same number.
uint32 alter_id = 2;
// Security settings. Only applies to client side.
xray.common.protocol.SecurityConfig security_settings = 3;
// Define tests enabled for this account
string tests_enabled = 4;
}

119
proxy/vmess/aead/authid.go Normal file
View file

@ -0,0 +1,119 @@
package aead
import (
"bytes"
"crypto/aes"
"crypto/cipher"
rand3 "crypto/rand"
"encoding/binary"
"errors"
"hash/crc32"
"io"
"math"
"time"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/antireplay"
)
var (
ErrNotFound = errors.New("user do not exist")
ErrReplay = errors.New("replayed request")
)
func CreateAuthID(cmdKey []byte, time int64) [16]byte {
buf := bytes.NewBuffer(nil)
common.Must(binary.Write(buf, binary.BigEndian, time))
var zero uint32
common.Must2(io.CopyN(buf, rand3.Reader, 4))
zero = crc32.ChecksumIEEE(buf.Bytes())
common.Must(binary.Write(buf, binary.BigEndian, zero))
aesBlock := NewCipherFromKey(cmdKey)
if buf.Len() != 16 {
panic("Size unexpected")
}
var result [16]byte
aesBlock.Encrypt(result[:], buf.Bytes())
return result
}
func NewCipherFromKey(cmdKey []byte) cipher.Block {
aesBlock, err := aes.NewCipher(KDF16(cmdKey, KDFSaltConstAuthIDEncryptionKey))
if err != nil {
panic(err)
}
return aesBlock
}
type AuthIDDecoder struct {
s cipher.Block
}
func NewAuthIDDecoder(cmdKey []byte) *AuthIDDecoder {
return &AuthIDDecoder{NewCipherFromKey(cmdKey)}
}
func (aidd *AuthIDDecoder) Decode(data [16]byte) (int64, uint32, int32, []byte) {
aidd.s.Decrypt(data[:], data[:])
var t int64
var zero uint32
var rand int32
reader := bytes.NewReader(data[:])
common.Must(binary.Read(reader, binary.BigEndian, &t))
common.Must(binary.Read(reader, binary.BigEndian, &rand))
common.Must(binary.Read(reader, binary.BigEndian, &zero))
return t, zero, rand, data[:]
}
func NewAuthIDDecoderHolder() *AuthIDDecoderHolder {
return &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antireplay.NewReplayFilter(120)}
}
type AuthIDDecoderHolder struct {
decoders map[string]*AuthIDDecoderItem
filter *antireplay.ReplayFilter
}
type AuthIDDecoderItem struct {
dec *AuthIDDecoder
ticket interface{}
}
func NewAuthIDDecoderItem(key [16]byte, ticket interface{}) *AuthIDDecoderItem {
return &AuthIDDecoderItem{
dec: NewAuthIDDecoder(key[:]),
ticket: ticket,
}
}
func (a *AuthIDDecoderHolder) AddUser(key [16]byte, ticket interface{}) {
a.decoders[string(key[:])] = NewAuthIDDecoderItem(key, ticket)
}
func (a *AuthIDDecoderHolder) RemoveUser(key [16]byte) {
delete(a.decoders, string(key[:]))
}
func (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {
for _, v := range a.decoders {
t, z, _, d := v.dec.Decode(authID)
if z != crc32.ChecksumIEEE(d[:12]) {
continue
}
if t < 0 {
continue
}
if math.Abs(math.Abs(float64(t))-float64(time.Now().Unix())) > 120 {
continue
}
if !a.filter.Check(authID[:]) {
return nil, ErrReplay
}
return v.ticket, nil
}
return nil, ErrNotFound
}

View file

@ -0,0 +1,127 @@
package aead
import (
"fmt"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCreateAuthID(t *testing.T) {
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
authid := CreateAuthID(key, time.Now().Unix())
fmt.Println(key)
fmt.Println(authid)
}
func TestCreateAuthIDAndDecode(t *testing.T) {
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
authid := CreateAuthID(key, time.Now().Unix())
fmt.Println(key)
fmt.Println(authid)
AuthDecoder := NewAuthIDDecoderHolder()
var keyw [16]byte
copy(keyw[:], key)
AuthDecoder.AddUser(keyw, "Demo User")
res, err := AuthDecoder.Match(authid)
fmt.Println(res)
fmt.Println(err)
assert.Equal(t, "Demo User", res)
assert.Nil(t, err)
}
func TestCreateAuthIDAndDecode2(t *testing.T) {
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
authid := CreateAuthID(key, time.Now().Unix())
fmt.Println(key)
fmt.Println(authid)
AuthDecoder := NewAuthIDDecoderHolder()
var keyw [16]byte
copy(keyw[:], key)
AuthDecoder.AddUser(keyw, "Demo User")
res, err := AuthDecoder.Match(authid)
fmt.Println(res)
fmt.Println(err)
assert.Equal(t, "Demo User", res)
assert.Nil(t, err)
key2 := KDF16([]byte("Demo Key for Auth ID Test2"), "Demo Path for Auth ID Test")
authid2 := CreateAuthID(key2, time.Now().Unix())
res2, err2 := AuthDecoder.Match(authid2)
assert.EqualError(t, err2, "user do not exist")
assert.Nil(t, res2)
}
func TestCreateAuthIDAndDecodeMassive(t *testing.T) {
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
authid := CreateAuthID(key, time.Now().Unix())
fmt.Println(key)
fmt.Println(authid)
AuthDecoder := NewAuthIDDecoderHolder()
var keyw [16]byte
copy(keyw[:], key)
AuthDecoder.AddUser(keyw, "Demo User")
res, err := AuthDecoder.Match(authid)
fmt.Println(res)
fmt.Println(err)
assert.Equal(t, "Demo User", res)
assert.Nil(t, err)
for i := 0; i <= 10000; i++ {
key2 := KDF16([]byte("Demo Key for Auth ID Test2"), "Demo Path for Auth ID Test", strconv.Itoa(i))
var keyw2 [16]byte
copy(keyw2[:], key2)
AuthDecoder.AddUser(keyw2, "Demo User"+strconv.Itoa(i))
}
authid3 := CreateAuthID(key, time.Now().Unix())
res2, err2 := AuthDecoder.Match(authid3)
assert.Equal(t, "Demo User", res2)
assert.Nil(t, err2)
}
func TestCreateAuthIDAndDecodeSuperMassive(t *testing.T) {
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
authid := CreateAuthID(key, time.Now().Unix())
fmt.Println(key)
fmt.Println(authid)
AuthDecoder := NewAuthIDDecoderHolder()
var keyw [16]byte
copy(keyw[:], key)
AuthDecoder.AddUser(keyw, "Demo User")
res, err := AuthDecoder.Match(authid)
fmt.Println(res)
fmt.Println(err)
assert.Equal(t, "Demo User", res)
assert.Nil(t, err)
for i := 0; i <= 1000000; i++ {
key2 := KDF16([]byte("Demo Key for Auth ID Test2"), "Demo Path for Auth ID Test", strconv.Itoa(i))
var keyw2 [16]byte
copy(keyw2[:], key2)
AuthDecoder.AddUser(keyw2, "Demo User"+strconv.Itoa(i))
}
authid3 := CreateAuthID(key, time.Now().Unix())
before := time.Now()
res2, err2 := AuthDecoder.Match(authid3)
after := time.Now()
assert.Equal(t, "Demo User", res2)
assert.Nil(t, err2)
fmt.Println(after.Sub(before).Seconds())
}

View file

@ -0,0 +1,14 @@
package aead
const (
KDFSaltConstAuthIDEncryptionKey = "AES Auth ID Encryption"
KDFSaltConstAEADRespHeaderLenKey = "AEAD Resp Header Len Key"
KDFSaltConstAEADRespHeaderLenIV = "AEAD Resp Header Len IV"
KDFSaltConstAEADRespHeaderPayloadKey = "AEAD Resp Header Key"
KDFSaltConstAEADRespHeaderPayloadIV = "AEAD Resp Header IV"
KDFSaltConstVMessAEADKDF = "VMess AEAD KDF"
KDFSaltConstVMessHeaderPayloadAEADKey = "VMess Header AEAD Key"
KDFSaltConstVMessHeaderPayloadAEADIV = "VMess Header AEAD Nonce"
KDFSaltConstVMessHeaderPayloadLengthAEADKey = "VMess Header AEAD Key_Length"
KDFSaltConstVMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length"
)

172
proxy/vmess/aead/encrypt.go Normal file
View file

@ -0,0 +1,172 @@
package aead
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"io"
"time"
"github.com/xtls/xray-core/v1/common"
)
func SealVMessAEADHeader(key [16]byte, data []byte) []byte {
generatedAuthID := CreateAuthID(key[:], time.Now().Unix())
connectionNonce := make([]byte, 8)
if _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil {
panic(err.Error())
}
aeadPayloadLengthSerializeBuffer := bytes.NewBuffer(nil)
headerPayloadDataLen := uint16(len(data))
common.Must(binary.Write(aeadPayloadLengthSerializeBuffer, binary.BigEndian, headerPayloadDataLen))
aeadPayloadLengthSerializedByte := aeadPayloadLengthSerializeBuffer.Bytes()
var payloadHeaderLengthAEADEncrypted []byte
{
payloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADKey, string(generatedAuthID[:]), string(connectionNonce))
payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
payloadHeaderLengthAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderLengthAEADAESBlock)
if err != nil {
panic(err.Error())
}
payloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:])
}
var payloadHeaderAEADEncrypted []byte
{
payloadHeaderAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce))
payloadHeaderAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock)
if err != nil {
panic(err.Error())
}
payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:])
}
var outputBuffer = bytes.NewBuffer(nil)
common.Must2(outputBuffer.Write(generatedAuthID[:])) // 16
common.Must2(outputBuffer.Write(payloadHeaderLengthAEADEncrypted)) // 2+16
common.Must2(outputBuffer.Write(connectionNonce)) // 8
common.Must2(outputBuffer.Write(payloadHeaderAEADEncrypted))
return outputBuffer.Bytes()
}
func OpenVMessAEADHeader(key [16]byte, authid [16]byte, data io.Reader) ([]byte, bool, int, error) {
var payloadHeaderLengthAEADEncrypted [18]byte
var nonce [8]byte
var bytesRead int
authidCheckValueReadBytesCounts, err := io.ReadFull(data, payloadHeaderLengthAEADEncrypted[:])
bytesRead += authidCheckValueReadBytesCounts
if err != nil {
return nil, false, bytesRead, err
}
nonceReadBytesCounts, err := io.ReadFull(data, nonce[:])
bytesRead += nonceReadBytesCounts
if err != nil {
return nil, false, bytesRead, err
}
// Decrypt Length
var decryptedAEADHeaderLengthPayloadResult []byte
{
payloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADKey, string(authid[:]), string(nonce[:]))
payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, string(authid[:]), string(nonce[:]))[:12]
payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderLengthAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock)
if err != nil {
panic(err.Error())
}
decryptedAEADHeaderLengthPayload, erropenAEAD := payloadHeaderLengthAEAD.Open(nil, payloadHeaderLengthAEADNonce, payloadHeaderLengthAEADEncrypted[:], authid[:])
if erropenAEAD != nil {
return nil, true, bytesRead, erropenAEAD
}
decryptedAEADHeaderLengthPayloadResult = decryptedAEADHeaderLengthPayload
}
var length uint16
common.Must(binary.Read(bytes.NewReader(decryptedAEADHeaderLengthPayloadResult), binary.BigEndian, &length))
var decryptedAEADHeaderPayloadR []byte
var payloadHeaderAEADEncryptedReadedBytesCounts int
{
payloadHeaderAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadAEADKey, string(authid[:]), string(nonce[:]))
payloadHeaderAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadAEADIV, string(authid[:]), string(nonce[:]))[:12]
// 16 == AEAD Tag size
payloadHeaderAEADEncrypted := make([]byte, length+16)
payloadHeaderAEADEncryptedReadedBytesCounts, err = io.ReadFull(data, payloadHeaderAEADEncrypted)
bytesRead += payloadHeaderAEADEncryptedReadedBytesCounts
if err != nil {
return nil, false, bytesRead, err
}
payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock)
if err != nil {
panic(err.Error())
}
decryptedAEADHeaderPayload, erropenAEAD := payloadHeaderAEAD.Open(nil, payloadHeaderAEADNonce, payloadHeaderAEADEncrypted, authid[:])
if erropenAEAD != nil {
return nil, true, bytesRead, erropenAEAD
}
decryptedAEADHeaderPayloadR = decryptedAEADHeaderPayload
}
return decryptedAEADHeaderPayloadR, false, bytesRead, nil
}

View file

@ -0,0 +1,104 @@
package aead
import (
"bytes"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOpenVMessAEADHeader(t *testing.T) {
TestHeader := []byte("Test Header")
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
var keyw [16]byte
copy(keyw[:], key)
sealed := SealVMessAEADHeader(keyw, TestHeader)
var AEADR = bytes.NewReader(sealed)
var authid [16]byte
io.ReadFull(AEADR, authid[:])
out, _, _, err := OpenVMessAEADHeader(keyw, authid, AEADR)
fmt.Println(string(out))
fmt.Println(err)
}
func TestOpenVMessAEADHeader2(t *testing.T) {
TestHeader := []byte("Test Header")
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
var keyw [16]byte
copy(keyw[:], key)
sealed := SealVMessAEADHeader(keyw, TestHeader)
var AEADR = bytes.NewReader(sealed)
var authid [16]byte
io.ReadFull(AEADR, authid[:])
out, _, readen, err := OpenVMessAEADHeader(keyw, authid, AEADR)
assert.Equal(t, len(sealed)-16-AEADR.Len(), readen)
assert.Equal(t, string(TestHeader), string(out))
assert.Nil(t, err)
}
func TestOpenVMessAEADHeader4(t *testing.T) {
for i := 0; i <= 60; i++ {
TestHeader := []byte("Test Header")
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
var keyw [16]byte
copy(keyw[:], key)
sealed := SealVMessAEADHeader(keyw, TestHeader)
var sealedm [16]byte
copy(sealedm[:], sealed)
sealed[i] ^= 0xff
var AEADR = bytes.NewReader(sealed)
var authid [16]byte
io.ReadFull(AEADR, authid[:])
out, drain, readen, err := OpenVMessAEADHeader(keyw, authid, AEADR)
assert.Equal(t, len(sealed)-16-AEADR.Len(), readen)
assert.Equal(t, true, drain)
assert.NotNil(t, err)
if err == nil {
fmt.Println(">")
}
assert.Nil(t, out)
}
}
func TestOpenVMessAEADHeader4Massive(t *testing.T) {
for j := 0; j < 1000; j++ {
for i := 0; i <= 60; i++ {
TestHeader := []byte("Test Header")
key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test")
var keyw [16]byte
copy(keyw[:], key)
sealed := SealVMessAEADHeader(keyw, TestHeader)
var sealedm [16]byte
copy(sealedm[:], sealed)
sealed[i] ^= 0xff
var AEADR = bytes.NewReader(sealed)
var authid [16]byte
io.ReadFull(AEADR, authid[:])
out, drain, readen, err := OpenVMessAEADHeader(keyw, authid, AEADR)
assert.Equal(t, len(sealed)-16-AEADR.Len(), readen)
assert.Equal(t, true, drain)
assert.NotNil(t, err)
if err == nil {
fmt.Println(">")
}
assert.Nil(t, out)
}
}
}

24
proxy/vmess/aead/kdf.go Normal file
View file

@ -0,0 +1,24 @@
package aead
import (
"crypto/hmac"
"crypto/sha256"
"hash"
)
func KDF(key []byte, path ...string) []byte {
hmacf := hmac.New(sha256.New, []byte(KDFSaltConstVMessAEADKDF))
for _, v := range path {
hmacf = hmac.New(func() hash.Hash {
return hmacf
}, []byte(v))
}
hmacf.Write(key)
return hmacf.Sum(nil)
}
func KDF16(key []byte, path ...string) []byte {
r := KDF(key, path...)
return r[:16]
}

View file

@ -0,0 +1,119 @@
package encoding
import (
"crypto/md5"
"encoding/binary"
"hash/fnv"
"github.com/xtls/xray-core/v1/common"
"golang.org/x/crypto/sha3"
)
// Authenticate authenticates a byte array using Fnv hash.
func Authenticate(b []byte) uint32 {
fnv1hash := fnv.New32a()
common.Must2(fnv1hash.Write(b))
return fnv1hash.Sum32()
}
type NoOpAuthenticator struct{}
func (NoOpAuthenticator) NonceSize() int {
return 0
}
func (NoOpAuthenticator) Overhead() int {
return 0
}
// Seal implements AEAD.Seal().
func (NoOpAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
return append(dst[:0], plaintext...)
}
// Open implements AEAD.Open().
func (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
return append(dst[:0], ciphertext...), nil
}
// FnvAuthenticator is an AEAD based on Fnv hash.
type FnvAuthenticator struct {
}
// NonceSize implements AEAD.NonceSize().
func (*FnvAuthenticator) NonceSize() int {
return 0
}
// Overhead impelements AEAD.Overhead().
func (*FnvAuthenticator) Overhead() int {
return 4
}
// Seal implements AEAD.Seal().
func (*FnvAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
dst = append(dst, 0, 0, 0, 0)
binary.BigEndian.PutUint32(dst, Authenticate(plaintext))
return append(dst, plaintext...)
}
// Open implements AEAD.Open().
func (*FnvAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if binary.BigEndian.Uint32(ciphertext[:4]) != Authenticate(ciphertext[4:]) {
return dst, newError("invalid authentication")
}
return append(dst, ciphertext[4:]...), nil
}
// GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array.
func GenerateChacha20Poly1305Key(b []byte) []byte {
key := make([]byte, 32)
t := md5.Sum(b)
copy(key, t[:])
t = md5.Sum(key[:16])
copy(key[16:], t[:])
return key
}
type ShakeSizeParser struct {
shake sha3.ShakeHash
buffer [2]byte
}
func NewShakeSizeParser(nonce []byte) *ShakeSizeParser {
shake := sha3.NewShake128()
common.Must2(shake.Write(nonce))
return &ShakeSizeParser{
shake: shake,
}
}
func (*ShakeSizeParser) SizeBytes() int32 {
return 2
}
func (s *ShakeSizeParser) next() uint16 {
common.Must2(s.shake.Read(s.buffer[:]))
return binary.BigEndian.Uint16(s.buffer[:])
}
func (s *ShakeSizeParser) Decode(b []byte) (uint16, error) {
mask := s.next()
size := binary.BigEndian.Uint16(b)
return mask ^ size, nil
}
func (s *ShakeSizeParser) Encode(size uint16, b []byte) []byte {
mask := s.next()
binary.BigEndian.PutUint16(b, mask^size)
return b[:2]
}
func (s *ShakeSizeParser) NextPaddingLen() uint16 {
return s.next() % 64
}
func (s *ShakeSizeParser) MaxPaddingLen() uint16 {
return 64
}

Some files were not shown because too many files have changed in this diff Show more