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

209
app/dispatcher/config.pb.go Normal file
View file

@ -0,0 +1,209 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: app/dispatcher/config.proto
package dispatcher
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 SessionConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SessionConfig) Reset() {
*x = SessionConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_app_dispatcher_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SessionConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SessionConfig) ProtoMessage() {}
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
mi := &file_app_dispatcher_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 SessionConfig.ProtoReflect.Descriptor instead.
func (*SessionConfig) Descriptor() ([]byte, []int) {
return file_app_dispatcher_config_proto_rawDescGZIP(), []int{0}
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_app_dispatcher_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_app_dispatcher_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_app_dispatcher_config_proto_rawDescGZIP(), []int{1}
}
func (x *Config) GetSettings() *SessionConfig {
if x != nil {
return x.Settings
}
return nil
}
var File_app_dispatcher_config_proto protoreflect.FileDescriptor
var file_app_dispatcher_config_proto_rawDesc = []byte{
0x0a, 0x1b, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68,
0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x48, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69,
0x6e, 0x67, 0x73, 0x42, 0x5e, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 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, 0x61,
0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa, 0x02, 0x13,
0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63,
0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_app_dispatcher_config_proto_rawDescOnce sync.Once
file_app_dispatcher_config_proto_rawDescData = file_app_dispatcher_config_proto_rawDesc
)
func file_app_dispatcher_config_proto_rawDescGZIP() []byte {
file_app_dispatcher_config_proto_rawDescOnce.Do(func() {
file_app_dispatcher_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dispatcher_config_proto_rawDescData)
})
return file_app_dispatcher_config_proto_rawDescData
}
var file_app_dispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_app_dispatcher_config_proto_goTypes = []interface{}{
(*SessionConfig)(nil), // 0: xray.app.dispatcher.SessionConfig
(*Config)(nil), // 1: xray.app.dispatcher.Config
}
var file_app_dispatcher_config_proto_depIdxs = []int32{
0, // 0: xray.app.dispatcher.Config.settings:type_name -> xray.app.dispatcher.SessionConfig
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_app_dispatcher_config_proto_init() }
func file_app_dispatcher_config_proto_init() {
if File_app_dispatcher_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_app_dispatcher_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SessionConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_app_dispatcher_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_app_dispatcher_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_app_dispatcher_config_proto_goTypes,
DependencyIndexes: file_app_dispatcher_config_proto_depIdxs,
MessageInfos: file_app_dispatcher_config_proto_msgTypes,
}.Build()
File_app_dispatcher_config_proto = out.File
file_app_dispatcher_config_proto_rawDesc = nil
file_app_dispatcher_config_proto_goTypes = nil
file_app_dispatcher_config_proto_depIdxs = nil
}

View file

@ -0,0 +1,15 @@
syntax = "proto3";
package xray.app.dispatcher;
option csharp_namespace = "Xray.App.Dispatcher";
option go_package = "github.com/xtls/xray-core/v1/app/dispatcher";
option java_package = "com.xray.app.dispatcher";
option java_multiple_files = true;
message SessionConfig {
reserved 1;
}
message Config {
SessionConfig settings = 1;
}

301
app/dispatcher/default.go Normal file
View file

@ -0,0 +1,301 @@
// +build !confonly
package dispatcher
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
import (
"context"
"strings"
"sync"
"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/core"
"github.com/xtls/xray-core/v1/features/outbound"
"github.com/xtls/xray-core/v1/features/policy"
"github.com/xtls/xray-core/v1/features/routing"
routing_session "github.com/xtls/xray-core/v1/features/routing/session"
"github.com/xtls/xray-core/v1/features/stats"
"github.com/xtls/xray-core/v1/transport"
"github.com/xtls/xray-core/v1/transport/pipe"
)
var (
errSniffingTimeout = newError("timeout on sniffing")
)
type cachedReader struct {
sync.Mutex
reader *pipe.Reader
cache buf.MultiBuffer
}
func (r *cachedReader) Cache(b *buf.Buffer) {
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
r.Lock()
if !mb.IsEmpty() {
r.cache, _ = buf.MergeMulti(r.cache, mb)
}
b.Clear()
rawBytes := b.Extend(buf.Size)
n := r.cache.Copy(rawBytes)
b.Resize(0, int32(n))
r.Unlock()
}
func (r *cachedReader) readInternal() buf.MultiBuffer {
r.Lock()
defer r.Unlock()
if r.cache != nil && !r.cache.IsEmpty() {
mb := r.cache
r.cache = nil
return mb
}
return nil
}
func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
mb := r.readInternal()
if mb != nil {
return mb, nil
}
return r.reader.ReadMultiBuffer()
}
func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) {
mb := r.readInternal()
if mb != nil {
return mb, nil
}
return r.reader.ReadMultiBufferTimeout(timeout)
}
func (r *cachedReader) Interrupt() {
r.Lock()
if r.cache != nil {
r.cache = buf.ReleaseMulti(r.cache)
}
r.Unlock()
r.reader.Interrupt()
}
// DefaultDispatcher is a default implementation of Dispatcher.
type DefaultDispatcher struct {
ohm outbound.Manager
router routing.Router
policy policy.Manager
stats stats.Manager
}
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
d := new(DefaultDispatcher)
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
return d.Init(config.(*Config), om, router, pm, sm)
}); err != nil {
return nil, err
}
return d, nil
}))
}
// Init initializes DefaultDispatcher.
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
d.ohm = om
d.router = router
d.policy = pm
d.stats = sm
return nil
}
// Type implements common.HasType.
func (*DefaultDispatcher) Type() interface{} {
return routing.DispatcherType()
}
// Start implements common.Runnable.
func (*DefaultDispatcher) Start() error {
return nil
}
// Close implements common.Closable.
func (*DefaultDispatcher) Close() error { return nil }
func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link) {
opt := pipe.OptionsFromContext(ctx)
uplinkReader, uplinkWriter := pipe.New(opt...)
downlinkReader, downlinkWriter := pipe.New(opt...)
inboundLink := &transport.Link{
Reader: downlinkReader,
Writer: uplinkWriter,
}
outboundLink := &transport.Link{
Reader: uplinkReader,
Writer: downlinkWriter,
}
sessionInbound := session.InboundFromContext(ctx)
var user *protocol.MemoryUser
if sessionInbound != nil {
user = sessionInbound.User
}
if user != nil && len(user.Email) > 0 {
p := d.policy.ForLevel(user.Level)
if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
inboundLink.Writer = &SizeStatWriter{
Counter: c,
Writer: inboundLink.Writer,
}
}
}
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
outboundLink.Writer = &SizeStatWriter{
Counter: c,
Writer: outboundLink.Writer,
}
}
}
}
return inboundLink, outboundLink
}
func shouldOverride(result SniffResult, domainOverride []string) bool {
for _, p := range domainOverride {
if strings.HasPrefix(result.Protocol(), p) {
return true
}
}
return false
}
// Dispatch implements routing.Dispatcher.
func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
if !destination.IsValid() {
panic("Dispatcher: Invalid destination.")
}
ob := &session.Outbound{
Target: destination,
}
ctx = session.ContextWithOutbound(ctx, ob)
inbound, outbound := d.getLink(ctx)
content := session.ContentFromContext(ctx)
if content == nil {
content = new(session.Content)
ctx = session.ContextWithContent(ctx, content)
}
sniffingRequest := content.SniffingRequest
if destination.Network != net.Network_TCP || !sniffingRequest.Enabled {
go d.routedDispatch(ctx, outbound, destination)
} else {
go func() {
cReader := &cachedReader{
reader: outbound.Reader.(*pipe.Reader),
}
outbound.Reader = cReader
result, err := sniffer(ctx, cReader)
if err == nil {
content.Protocol = result.Protocol()
}
if err == nil && shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) {
domain := result.Domain()
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain)
ob.Target = destination
}
d.routedDispatch(ctx, outbound, destination)
}()
}
return inbound, nil
}
func sniffer(ctx context.Context, cReader *cachedReader) (SniffResult, error) {
payload := buf.New()
defer payload.Release()
sniffer := NewSniffer()
totalAttempt := 0
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
totalAttempt++
if totalAttempt > 2 {
return nil, errSniffingTimeout
}
cReader.Cache(payload)
if !payload.IsEmpty() {
result, err := sniffer.Sniff(payload.Bytes())
if err != common.ErrNoClue {
return result, err
}
}
if payload.IsFull() {
return nil, errUnknownContent
}
}
}
}
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
var handler outbound.Handler
skipRoutePick := false
if content := session.ContentFromContext(ctx); content != nil {
skipRoutePick = content.SkipRoutePick
}
if d.router != nil && !skipRoutePick {
if route, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil {
tag := route.GetOutboundTag()
if h := d.ohm.GetHandler(tag); h != nil {
newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
handler = h
} else {
newError("non existing tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
}
} else {
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
}
}
if handler == nil {
handler = d.ohm.GetDefaultHandler()
}
if handler == nil {
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer)
common.Interrupt(link.Reader)
return
}
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
if tag := handler.Tag(); tag != "" {
accessMessage.Detour = tag
}
log.Record(accessMessage)
}
handler.Dispatch(ctx, link)
}

View file

@ -0,0 +1,5 @@
// +build !confonly
package dispatcher
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen

View file

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

55
app/dispatcher/sniffer.go Normal file
View file

@ -0,0 +1,55 @@
// +build !confonly
package dispatcher
import (
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/protocol/bittorrent"
"github.com/xtls/xray-core/v1/common/protocol/http"
"github.com/xtls/xray-core/v1/common/protocol/tls"
)
type SniffResult interface {
Protocol() string
Domain() string
}
type protocolSniffer func([]byte) (SniffResult, error)
type Sniffer struct {
sniffer []protocolSniffer
}
func NewSniffer() *Sniffer {
return &Sniffer{
sniffer: []protocolSniffer{
func(b []byte) (SniffResult, error) { return http.SniffHTTP(b) },
func(b []byte) (SniffResult, error) { return tls.SniffTLS(b) },
func(b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) },
},
}
}
var errUnknownContent = newError("unknown content")
func (s *Sniffer) Sniff(payload []byte) (SniffResult, error) {
var pendingSniffer []protocolSniffer
for _, s := range s.sniffer {
result, err := s(payload)
if err == common.ErrNoClue {
pendingSniffer = append(pendingSniffer, s)
continue
}
if err == nil && result != nil {
return result, nil
}
}
if len(pendingSniffer) > 0 {
s.sniffer = pendingSniffer
return nil, common.ErrNoClue
}
return nil, errUnknownContent
}

27
app/dispatcher/stats.go Normal file
View file

@ -0,0 +1,27 @@
// +build !confonly
package dispatcher
import (
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
"github.com/xtls/xray-core/v1/features/stats"
)
type SizeStatWriter struct {
Counter stats.Counter
Writer buf.Writer
}
func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.Counter.Add(int64(mb.Len()))
return w.Writer.WriteMultiBuffer(mb)
}
func (w *SizeStatWriter) Close() error {
return common.Close(w.Writer)
}
func (w *SizeStatWriter) Interrupt() {
common.Interrupt(w.Writer)
}

View file

@ -0,0 +1,44 @@
package dispatcher_test
import (
"testing"
. "github.com/xtls/xray-core/v1/app/dispatcher"
"github.com/xtls/xray-core/v1/common"
"github.com/xtls/xray-core/v1/common/buf"
)
type TestCounter int64
func (c *TestCounter) Value() int64 {
return int64(*c)
}
func (c *TestCounter) Add(v int64) int64 {
x := int64(*c) + v
*c = TestCounter(x)
return x
}
func (c *TestCounter) Set(v int64) int64 {
*c = TestCounter(v)
return v
}
func TestStatsWriter(t *testing.T) {
var c TestCounter
writer := &SizeStatWriter{
Counter: &c,
Writer: buf.Discard,
}
mb := buf.MergeBytes(nil, []byte("abcd"))
common.Must(writer.WriteMultiBuffer(mb))
mb = buf.MergeBytes(nil, []byte("efg"))
common.Must(writer.WriteMultiBuffer(mb))
if c.Value() != 7 {
t.Fatal("unexpected counter value. want 7, but got ", c.Value())
}
}