From 707efd6d1291a33440f73d042a3baa14122caadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 19 Sep 2021 20:28:38 +0800 Subject: [PATCH] Add loopback outound --- infra/conf/blackhole.go | 1 - infra/conf/loopback.go | 15 +++ infra/conf/xray.go | 1 + main/distro/all/all.go | 1 + proxy/loopback/config.go | 3 + proxy/loopback/config.pb.go | 149 +++++++++++++++++++++++++++++ proxy/loopback/config.proto | 11 +++ proxy/loopback/errors.generated.go | 9 ++ proxy/loopback/loopback.go | 121 +++++++++++++++++++++++ 9 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 infra/conf/loopback.go create mode 100644 proxy/loopback/config.go create mode 100644 proxy/loopback/config.pb.go create mode 100644 proxy/loopback/config.proto create mode 100644 proxy/loopback/errors.generated.go create mode 100644 proxy/loopback/loopback.go diff --git a/infra/conf/blackhole.go b/infra/conf/blackhole.go index e2fdeedc..a8d4e632 100644 --- a/infra/conf/blackhole.go +++ b/infra/conf/blackhole.go @@ -2,7 +2,6 @@ package conf import ( "encoding/json" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/serial" diff --git a/infra/conf/loopback.go b/infra/conf/loopback.go new file mode 100644 index 00000000..93b1537c --- /dev/null +++ b/infra/conf/loopback.go @@ -0,0 +1,15 @@ +package conf + +import ( + "github.com/golang/protobuf/proto" + + "github.com/xtls/xray-core/proxy/loopback" +) + +type LoopbackConfig struct { + InboundTag string `json:"inboundTag"` +} + +func (l LoopbackConfig) Build() (proto.Message, error) { + return &loopback.Config{InboundTag: l.InboundTag}, nil +} diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 28a155c9..eaed946d 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -30,6 +30,7 @@ var ( outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ "blackhole": func() interface{} { return new(BlackholeConfig) }, + "loopback": func() interface{} { return new(LoopbackConfig) }, "freedom": func() interface{} { return new(FreedomConfig) }, "http": func() interface{} { return new(HTTPClientConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 166b2b9b..3653329a 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -32,6 +32,7 @@ import ( _ "github.com/xtls/xray-core/proxy/dokodemo" _ "github.com/xtls/xray-core/proxy/freedom" _ "github.com/xtls/xray-core/proxy/http" + _ "github.com/xtls/xray-core/proxy/loopback" _ "github.com/xtls/xray-core/proxy/mtproto" _ "github.com/xtls/xray-core/proxy/shadowsocks" _ "github.com/xtls/xray-core/proxy/socks" diff --git a/proxy/loopback/config.go b/proxy/loopback/config.go new file mode 100644 index 00000000..95b1ae94 --- /dev/null +++ b/proxy/loopback/config.go @@ -0,0 +1,3 @@ +package loopback + +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/proxy/loopback/config.pb.go b/proxy/loopback/config.pb.go new file mode 100644 index 00000000..319f42e7 --- /dev/null +++ b/proxy/loopback/config.pb.go @@ -0,0 +1,149 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.18.0 +// source: proxy/loopback/config.proto + +package loopback + +import ( + 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) +) + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_loopback_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_loopback_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_loopback_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetInboundTag() string { + if x != nil { + return x.InboundTag + } + return "" +} + +var File_proxy_loopback_config_proto protoreflect.FileDescriptor + +var file_proxy_loopback_config_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, + 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x76, + 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, + 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x22, 0x29, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x54, 0x61, 0x67, 0x42, 0x5b, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 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, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0xaa, 0x02, 0x13, 0x58, 0x72, 0x61, + 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proxy_loopback_config_proto_rawDescOnce sync.Once + file_proxy_loopback_config_proto_rawDescData = file_proxy_loopback_config_proto_rawDesc +) + +func file_proxy_loopback_config_proto_rawDescGZIP() []byte { + file_proxy_loopback_config_proto_rawDescOnce.Do(func() { + file_proxy_loopback_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_loopback_config_proto_rawDescData) + }) + return file_proxy_loopback_config_proto_rawDescData +} + +var file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_proxy_loopback_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: v2ray.core.proxy.loopback.Config +} +var file_proxy_loopback_config_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_loopback_config_proto_init() } +func file_proxy_loopback_config_proto_init() { + if File_proxy_loopback_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proxy_loopback_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_loopback_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proxy_loopback_config_proto_goTypes, + DependencyIndexes: file_proxy_loopback_config_proto_depIdxs, + MessageInfos: file_proxy_loopback_config_proto_msgTypes, + }.Build() + File_proxy_loopback_config_proto = out.File + file_proxy_loopback_config_proto_rawDesc = nil + file_proxy_loopback_config_proto_goTypes = nil + file_proxy_loopback_config_proto_depIdxs = nil +} diff --git a/proxy/loopback/config.proto b/proxy/loopback/config.proto new file mode 100644 index 00000000..64f9e119 --- /dev/null +++ b/proxy/loopback/config.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package v2ray.core.proxy.loopback; +option csharp_namespace = "Xray.Proxy.Loopback"; +option go_package = "github.com/xtls/xray-core/proxy/loopback"; +option java_package = "com.xray.proxy.loopback"; +option java_multiple_files = true; + +message Config { + string inbound_tag = 1; +} diff --git a/proxy/loopback/errors.generated.go b/proxy/loopback/errors.generated.go new file mode 100644 index 00000000..0db02932 --- /dev/null +++ b/proxy/loopback/errors.generated.go @@ -0,0 +1,9 @@ +package loopback + +import "github.com/xtls/xray-core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/loopback/loopback.go b/proxy/loopback/loopback.go new file mode 100644 index 00000000..ab19fc20 --- /dev/null +++ b/proxy/loopback/loopback.go @@ -0,0 +1,121 @@ +package loopback + +import ( + "context" + "github.com/xtls/xray-core/common/net/cnc" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/retry" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/task" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/transport" + "github.com/xtls/xray-core/transport/internet" +) + +type Loopback struct { + config *Config + dispatcherInstance routing.Dispatcher +} + +func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet.Dialer) error { + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified.") + } + destination := outbound.Target + + newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx)) + + input := link.Reader + output := link.Writer + + var conn net.Conn + err := retry.ExponentialBackoff(2, 100).On(func() error { + dialDest := destination + + content := new(session.Content) + content.SkipDNSResolve = true + + ctx = session.ContextWithContent(ctx, content) + + inbound := session.InboundFromContext(ctx) + + inbound.Tag = l.config.InboundTag + + ctx = session.ContextWithInbound(ctx, inbound) + + rawConn, err := l.dispatcherInstance.Dispatch(ctx, dialDest) + if err != nil { + return err + } + + var readerOpt cnc.ConnectionOption + if dialDest.Network == net.Network_TCP { + readerOpt = cnc.ConnectionOutputMulti(rawConn.Reader) + } else { + readerOpt = cnc.ConnectionOutputMultiUDP(rawConn.Reader) + } + + conn = cnc.NewConnection(cnc.ConnectionInputMulti(rawConn.Writer), readerOpt) + return nil + }) + if err != nil { + return newError("failed to open connection to ", destination).Base(err) + } + defer conn.Close() + + requestDone := func() error { + 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); err != nil { + return newError("failed to process request").Base(err) + } + + return nil + } + + responseDone := func() error { + 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); 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 +} + +func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error { + l.dispatcherInstance = dispatcherInstance + l.config = config + return nil +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + l := new(Loopback) + err := core.RequireFeatures(ctx, func(dispatcherInstance routing.Dispatcher) error { + return l.init(config.(*Config), dispatcherInstance) + }) + return l, err + })) +}