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