mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-30 09:18:34 +00:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
711 changed files with 82154 additions and 2 deletions
150
app/proxyman/command/command.go
Normal file
150
app/proxyman/command/command.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
// +build !confonly
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/features/inbound"
|
||||
"github.com/xtls/xray-core/v1/features/outbound"
|
||||
"github.com/xtls/xray-core/v1/proxy"
|
||||
)
|
||||
|
||||
// InboundOperation is the interface for operations that applies to inbound handlers.
|
||||
type InboundOperation interface {
|
||||
// ApplyInbound applies this operation to the given inbound handler.
|
||||
ApplyInbound(context.Context, inbound.Handler) error
|
||||
}
|
||||
|
||||
// OutboundOperation is the interface for operations that applies to outbound handlers.
|
||||
type OutboundOperation interface {
|
||||
// ApplyOutbound applies this operation to the given outbound handler.
|
||||
ApplyOutbound(context.Context, outbound.Handler) error
|
||||
}
|
||||
|
||||
func getInbound(handler inbound.Handler) (proxy.Inbound, error) {
|
||||
gi, ok := handler.(proxy.GetInbound)
|
||||
if !ok {
|
||||
return nil, newError("can't get inbound proxy from handler.")
|
||||
}
|
||||
return gi.GetInbound(), nil
|
||||
}
|
||||
|
||||
// ApplyInbound implements InboundOperation.
|
||||
func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error {
|
||||
p, err := getInbound(handler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
um, ok := p.(proxy.UserManager)
|
||||
if !ok {
|
||||
return newError("proxy is not a UserManager")
|
||||
}
|
||||
mUser, err := op.User.ToMemoryUser()
|
||||
if err != nil {
|
||||
return newError("failed to parse user").Base(err)
|
||||
}
|
||||
return um.AddUser(ctx, mUser)
|
||||
}
|
||||
|
||||
// ApplyInbound implements InboundOperation.
|
||||
func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error {
|
||||
p, err := getInbound(handler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
um, ok := p.(proxy.UserManager)
|
||||
if !ok {
|
||||
return newError("proxy is not a UserManager")
|
||||
}
|
||||
return um.RemoveUser(ctx, op.Email)
|
||||
}
|
||||
|
||||
type handlerServer struct {
|
||||
s *core.Instance
|
||||
ihm inbound.Manager
|
||||
ohm outbound.Manager
|
||||
}
|
||||
|
||||
func (s *handlerServer) AddInbound(ctx context.Context, request *AddInboundRequest) (*AddInboundResponse, error) {
|
||||
if err := core.AddInboundHandler(s.s, request.Inbound); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AddInboundResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboundRequest) (*RemoveInboundResponse, error) {
|
||||
return &RemoveInboundResponse{}, s.ihm.RemoveHandler(ctx, request.Tag)
|
||||
}
|
||||
|
||||
func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) {
|
||||
rawOperation, err := request.Operation.GetInstance()
|
||||
if err != nil {
|
||||
return nil, newError("unknown operation").Base(err)
|
||||
}
|
||||
operation, ok := rawOperation.(InboundOperation)
|
||||
if !ok {
|
||||
return nil, newError("not an inbound operation")
|
||||
}
|
||||
|
||||
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||
if err != nil {
|
||||
return nil, newError("failed to get handler: ", request.Tag).Base(err)
|
||||
}
|
||||
|
||||
return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler)
|
||||
}
|
||||
|
||||
func (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) {
|
||||
if err := core.AddOutboundHandler(s.s, request.Outbound); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AddOutboundResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutboundRequest) (*RemoveOutboundResponse, error) {
|
||||
return &RemoveOutboundResponse{}, s.ohm.RemoveHandler(ctx, request.Tag)
|
||||
}
|
||||
|
||||
func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) {
|
||||
rawOperation, err := request.Operation.GetInstance()
|
||||
if err != nil {
|
||||
return nil, newError("unknown operation").Base(err)
|
||||
}
|
||||
operation, ok := rawOperation.(OutboundOperation)
|
||||
if !ok {
|
||||
return nil, newError("not an outbound operation")
|
||||
}
|
||||
|
||||
handler := s.ohm.GetHandler(request.Tag)
|
||||
return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler)
|
||||
}
|
||||
|
||||
func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {}
|
||||
|
||||
type service struct {
|
||||
v *core.Instance
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) {
|
||||
hs := &handlerServer{
|
||||
s: s.v,
|
||||
}
|
||||
common.Must(s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) {
|
||||
hs.ihm = im
|
||||
hs.ohm = om
|
||||
}))
|
||||
RegisterHandlerServiceServer(server, hs)
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||
s := core.MustFromContext(ctx)
|
||||
return &service{v: s}, nil
|
||||
}))
|
||||
}
|
1065
app/proxyman/command/command.pb.go
Normal file
1065
app/proxyman/command/command.pb.go
Normal file
File diff suppressed because it is too large
Load diff
73
app/proxyman/command/command.proto
Normal file
73
app/proxyman/command/command.proto
Normal file
|
@ -0,0 +1,73 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.app.proxyman.command;
|
||||
option csharp_namespace = "Xray.App.Proxyman.Command";
|
||||
option go_package = "github.com/xtls/xray-core/v1/app/proxyman/command";
|
||||
option java_package = "com.xray.app.proxyman.command";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/protocol/user.proto";
|
||||
import "common/serial/typed_message.proto";
|
||||
import "core/config.proto";
|
||||
|
||||
message AddUserOperation {
|
||||
xray.common.protocol.User user = 1;
|
||||
}
|
||||
|
||||
message RemoveUserOperation {
|
||||
string email = 1;
|
||||
}
|
||||
|
||||
message AddInboundRequest {
|
||||
core.InboundHandlerConfig inbound = 1;
|
||||
}
|
||||
|
||||
message AddInboundResponse {}
|
||||
|
||||
message RemoveInboundRequest {
|
||||
string tag = 1;
|
||||
}
|
||||
|
||||
message RemoveInboundResponse {}
|
||||
|
||||
message AlterInboundRequest {
|
||||
string tag = 1;
|
||||
xray.common.serial.TypedMessage operation = 2;
|
||||
}
|
||||
|
||||
message AlterInboundResponse {}
|
||||
|
||||
message AddOutboundRequest {
|
||||
core.OutboundHandlerConfig outbound = 1;
|
||||
}
|
||||
|
||||
message AddOutboundResponse {}
|
||||
|
||||
message RemoveOutboundRequest {
|
||||
string tag = 1;
|
||||
}
|
||||
|
||||
message RemoveOutboundResponse {}
|
||||
|
||||
message AlterOutboundRequest {
|
||||
string tag = 1;
|
||||
xray.common.serial.TypedMessage operation = 2;
|
||||
}
|
||||
|
||||
message AlterOutboundResponse {}
|
||||
|
||||
service HandlerService {
|
||||
rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {}
|
||||
|
||||
rpc RemoveInbound(RemoveInboundRequest) returns (RemoveInboundResponse) {}
|
||||
|
||||
rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {}
|
||||
|
||||
rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {}
|
||||
|
||||
rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {}
|
||||
|
||||
rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {}
|
||||
}
|
||||
|
||||
message Config {}
|
277
app/proxyman/command/command_grpc.pb.go
Normal file
277
app/proxyman/command/command_grpc.pb.go
Normal file
|
@ -0,0 +1,277 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// HandlerServiceClient is the client API for HandlerService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type HandlerServiceClient interface {
|
||||
AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error)
|
||||
RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error)
|
||||
AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error)
|
||||
AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error)
|
||||
RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error)
|
||||
AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error)
|
||||
}
|
||||
|
||||
type handlerServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewHandlerServiceClient(cc grpc.ClientConnInterface) HandlerServiceClient {
|
||||
return &handlerServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) {
|
||||
out := new(AddInboundResponse)
|
||||
err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AddInbound", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) {
|
||||
out := new(RemoveInboundResponse)
|
||||
err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/RemoveInbound", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) {
|
||||
out := new(AlterInboundResponse)
|
||||
err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AlterInbound", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) {
|
||||
out := new(AddOutboundResponse)
|
||||
err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AddOutbound", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) {
|
||||
out := new(RemoveOutboundResponse)
|
||||
err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/RemoveOutbound", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) {
|
||||
out := new(AlterOutboundResponse)
|
||||
err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AlterOutbound", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// HandlerServiceServer is the server API for HandlerService service.
|
||||
// All implementations must embed UnimplementedHandlerServiceServer
|
||||
// for forward compatibility
|
||||
type HandlerServiceServer interface {
|
||||
AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error)
|
||||
RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error)
|
||||
AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error)
|
||||
AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error)
|
||||
RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error)
|
||||
AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error)
|
||||
mustEmbedUnimplementedHandlerServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedHandlerServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedHandlerServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedHandlerServiceServer) AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddInbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RemoveInbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AlterInbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddOutbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RemoveOutbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AlterOutbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {}
|
||||
|
||||
// UnsafeHandlerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to HandlerServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeHandlerServiceServer interface {
|
||||
mustEmbedUnimplementedHandlerServiceServer()
|
||||
}
|
||||
|
||||
func RegisterHandlerServiceServer(s grpc.ServiceRegistrar, srv HandlerServiceServer) {
|
||||
s.RegisterService(&_HandlerService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _HandlerService_AddInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddInboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).AddInbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/xray.app.proxyman.command.HandlerService/AddInbound",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).AddInbound(ctx, req.(*AddInboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_RemoveInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RemoveInboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).RemoveInbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/xray.app.proxyman.command.HandlerService/RemoveInbound",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).RemoveInbound(ctx, req.(*RemoveInboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AlterInboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).AlterInbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/xray.app.proxyman.command.HandlerService/AlterInbound",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).AlterInbound(ctx, req.(*AlterInboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddOutboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).AddOutbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/xray.app.proxyman.command.HandlerService/AddOutbound",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).AddOutbound(ctx, req.(*AddOutboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_RemoveOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RemoveOutboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).RemoveOutbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/xray.app.proxyman.command.HandlerService/RemoveOutbound",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).RemoveOutbound(ctx, req.(*RemoveOutboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AlterOutboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).AlterOutbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/xray.app.proxyman.command.HandlerService/AlterOutbound",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).AlterOutbound(ctx, req.(*AlterOutboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _HandlerService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "xray.app.proxyman.command.HandlerService",
|
||||
HandlerType: (*HandlerServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "AddInbound",
|
||||
Handler: _HandlerService_AddInbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RemoveInbound",
|
||||
Handler: _HandlerService_RemoveInbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AlterInbound",
|
||||
Handler: _HandlerService_AlterInbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AddOutbound",
|
||||
Handler: _HandlerService_AddOutbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RemoveOutbound",
|
||||
Handler: _HandlerService_RemoveOutbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AlterOutbound",
|
||||
Handler: _HandlerService_AlterOutbound_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "app/proxyman/command/command.proto",
|
||||
}
|
3
app/proxyman/command/doc.go
Normal file
3
app/proxyman/command/doc.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package command
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
9
app/proxyman/command/errors.generated.go
Normal file
9
app/proxyman/command/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package command
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
39
app/proxyman/config.go
Normal file
39
app/proxyman/config.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package proxyman
|
||||
|
||||
func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
|
||||
if s == nil || s.Concurrency == nil {
|
||||
return 3
|
||||
}
|
||||
return s.Concurrency.Value
|
||||
}
|
||||
|
||||
func (s *AllocationStrategy) GetRefreshValue() uint32 {
|
||||
if s == nil || s.Refresh == nil {
|
||||
return 5
|
||||
}
|
||||
return s.Refresh.Value
|
||||
}
|
||||
|
||||
func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig {
|
||||
if c.SniffingSettings != nil {
|
||||
return c.SniffingSettings
|
||||
}
|
||||
|
||||
if len(c.DomainOverride) > 0 {
|
||||
var p []string
|
||||
for _, kd := range c.DomainOverride {
|
||||
switch kd {
|
||||
case KnownProtocols_HTTP:
|
||||
p = append(p, "http")
|
||||
case KnownProtocols_TLS:
|
||||
p = append(p, "tls")
|
||||
}
|
||||
}
|
||||
return &SniffingConfig{
|
||||
Enabled: true,
|
||||
DestinationOverride: p,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
1049
app/proxyman/config.pb.go
Normal file
1049
app/proxyman/config.pb.go
Normal file
File diff suppressed because it is too large
Load diff
97
app/proxyman/config.proto
Normal file
97
app/proxyman/config.proto
Normal file
|
@ -0,0 +1,97 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.app.proxyman;
|
||||
option csharp_namespace = "Xray.App.Proxyman";
|
||||
option go_package = "github.com/xtls/xray-core/v1/app/proxyman";
|
||||
option java_package = "com.xray.app.proxyman";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/net/address.proto";
|
||||
import "common/net/port.proto";
|
||||
import "transport/internet/config.proto";
|
||||
import "common/serial/typed_message.proto";
|
||||
|
||||
message InboundConfig {}
|
||||
|
||||
message AllocationStrategy {
|
||||
enum Type {
|
||||
// Always allocate all connection handlers.
|
||||
Always = 0;
|
||||
|
||||
// Randomly allocate specific range of handlers.
|
||||
Random = 1;
|
||||
|
||||
// External. Not supported yet.
|
||||
External = 2;
|
||||
}
|
||||
|
||||
Type type = 1;
|
||||
|
||||
message AllocationStrategyConcurrency {
|
||||
uint32 value = 1;
|
||||
}
|
||||
|
||||
// Number of handlers (ports) running in parallel.
|
||||
// Default value is 3 if unset.
|
||||
AllocationStrategyConcurrency concurrency = 2;
|
||||
|
||||
message AllocationStrategyRefresh {
|
||||
uint32 value = 1;
|
||||
}
|
||||
|
||||
// Number of minutes before a handler is regenerated.
|
||||
// Default value is 5 if unset.
|
||||
AllocationStrategyRefresh refresh = 3;
|
||||
}
|
||||
|
||||
enum KnownProtocols {
|
||||
HTTP = 0;
|
||||
TLS = 1;
|
||||
}
|
||||
|
||||
message SniffingConfig {
|
||||
// Whether or not to enable content sniffing on an inbound connection.
|
||||
bool enabled = 1;
|
||||
|
||||
// Override target destination if sniff'ed protocol is in the given list.
|
||||
// Supported values are "http", "tls".
|
||||
repeated string destination_override = 2;
|
||||
}
|
||||
|
||||
message ReceiverConfig {
|
||||
// PortRange specifies the ports which the Receiver should listen on.
|
||||
xray.common.net.PortRange port_range = 1;
|
||||
// Listen specifies the IP address that the Receiver should listen on.
|
||||
xray.common.net.IPOrDomain listen = 2;
|
||||
AllocationStrategy allocation_strategy = 3;
|
||||
xray.transport.internet.StreamConfig stream_settings = 4;
|
||||
bool receive_original_destination = 5;
|
||||
reserved 6;
|
||||
// Override domains for the given protocol.
|
||||
// Deprecated. Use sniffing_settings.
|
||||
repeated KnownProtocols domain_override = 7 [deprecated = true];
|
||||
SniffingConfig sniffing_settings = 8;
|
||||
}
|
||||
|
||||
message InboundHandlerConfig {
|
||||
string tag = 1;
|
||||
xray.common.serial.TypedMessage receiver_settings = 2;
|
||||
xray.common.serial.TypedMessage proxy_settings = 3;
|
||||
}
|
||||
|
||||
message OutboundConfig {}
|
||||
|
||||
message SenderConfig {
|
||||
// Send traffic through the given IP. Only IP is allowed.
|
||||
xray.common.net.IPOrDomain via = 1;
|
||||
xray.transport.internet.StreamConfig stream_settings = 2;
|
||||
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
||||
MultiplexingConfig multiplex_settings = 4;
|
||||
}
|
||||
|
||||
message MultiplexingConfig {
|
||||
// Whether or not Mux is enabled.
|
||||
bool enabled = 1;
|
||||
// Max number of concurrent connections that one Mux connection can handle.
|
||||
uint32 concurrency = 2;
|
||||
}
|
185
app/proxyman/inbound/always.go
Normal file
185
app/proxyman/inbound/always.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/proxyman"
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/dice"
|
||||
"github.com/xtls/xray-core/v1/common/errors"
|
||||
"github.com/xtls/xray-core/v1/common/mux"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"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"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
)
|
||||
|
||||
func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
|
||||
var uplinkCounter stats.Counter
|
||||
var downlinkCounter stats.Counter
|
||||
|
||||
policy := v.GetFeature(policy.ManagerType()).(policy.Manager)
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
uplinkCounter = c
|
||||
}
|
||||
}
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
}
|
||||
|
||||
return uplinkCounter, downlinkCounter
|
||||
}
|
||||
|
||||
type AlwaysOnInboundHandler struct {
|
||||
proxy proxy.Inbound
|
||||
workers []worker
|
||||
mux *mux.Server
|
||||
tag string
|
||||
}
|
||||
|
||||
func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
|
||||
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, ok := rawProxy.(proxy.Inbound)
|
||||
if !ok {
|
||||
return nil, newError("not an inbound proxy.")
|
||||
}
|
||||
|
||||
h := &AlwaysOnInboundHandler{
|
||||
proxy: p,
|
||||
mux: mux.NewServer(ctx),
|
||||
tag: tag,
|
||||
}
|
||||
|
||||
uplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag)
|
||||
|
||||
nl := p.Network()
|
||||
pr := receiverConfig.PortRange
|
||||
address := receiverConfig.Listen.AsAddress()
|
||||
if address == nil {
|
||||
address = net.AnyIP
|
||||
}
|
||||
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse stream config").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
if receiverConfig.ReceiveOriginalDestination {
|
||||
if mss.SocketSettings == nil {
|
||||
mss.SocketSettings = &internet.SocketConfig{}
|
||||
}
|
||||
if mss.SocketSettings.Tproxy == internet.SocketConfig_Off {
|
||||
mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect
|
||||
}
|
||||
mss.SocketSettings.ReceiveOriginalDestAddress = true
|
||||
}
|
||||
if pr == nil {
|
||||
if net.HasNetwork(nl, net.Network_UNIX) {
|
||||
newError("creating unix domain socket worker on ", address).AtDebug().WriteToLog()
|
||||
|
||||
worker := &dsWorker{
|
||||
address: address,
|
||||
proxy: p,
|
||||
stream: mss,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
}
|
||||
h.workers = append(h.workers, worker)
|
||||
}
|
||||
}
|
||||
if pr != nil {
|
||||
for port := pr.From; port <= pr.To; port++ {
|
||||
if net.HasNetwork(nl, net.Network_TCP) {
|
||||
newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog()
|
||||
|
||||
worker := &tcpWorker{
|
||||
address: address,
|
||||
port: net.Port(port),
|
||||
proxy: p,
|
||||
stream: mss,
|
||||
recvOrigDest: receiverConfig.ReceiveOriginalDestination,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
}
|
||||
h.workers = append(h.workers, worker)
|
||||
}
|
||||
|
||||
if net.HasNetwork(nl, net.Network_UDP) {
|
||||
worker := &udpWorker{
|
||||
tag: tag,
|
||||
proxy: p,
|
||||
address: address,
|
||||
port: net.Port(port),
|
||||
dispatcher: h.mux,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
stream: mss,
|
||||
}
|
||||
h.workers = append(h.workers, worker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (h *AlwaysOnInboundHandler) Start() error {
|
||||
for _, worker := range h.workers {
|
||||
if err := worker.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (h *AlwaysOnInboundHandler) Close() error {
|
||||
var errs []error
|
||||
for _, worker := range h.workers {
|
||||
errs = append(errs, worker.Close())
|
||||
}
|
||||
errs = append(errs, h.mux.Close())
|
||||
if err := errors.Combine(errs...); err != nil {
|
||||
return newError("failed to close all resources").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
|
||||
if len(h.workers) == 0 {
|
||||
return nil, 0, 0
|
||||
}
|
||||
w := h.workers[dice.Roll(len(h.workers))]
|
||||
return w.Proxy(), w.Port(), 9999
|
||||
}
|
||||
|
||||
func (h *AlwaysOnInboundHandler) Tag() string {
|
||||
return h.tag
|
||||
}
|
||||
|
||||
func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound {
|
||||
return h.proxy
|
||||
}
|
201
app/proxyman/inbound/dynamic.go
Normal file
201
app/proxyman/inbound/dynamic.go
Normal file
|
@ -0,0 +1,201 @@
|
|||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/proxyman"
|
||||
"github.com/xtls/xray-core/v1/common/dice"
|
||||
"github.com/xtls/xray-core/v1/common/mux"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/proxy"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
)
|
||||
|
||||
type DynamicInboundHandler struct {
|
||||
tag string
|
||||
v *core.Instance
|
||||
proxyConfig interface{}
|
||||
receiverConfig *proxyman.ReceiverConfig
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
portMutex sync.Mutex
|
||||
portsInUse map[net.Port]bool
|
||||
workerMutex sync.RWMutex
|
||||
worker []worker
|
||||
lastRefresh time.Time
|
||||
mux *mux.Server
|
||||
task *task.Periodic
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
h := &DynamicInboundHandler{
|
||||
tag: tag,
|
||||
proxyConfig: proxyConfig,
|
||||
receiverConfig: receiverConfig,
|
||||
portsInUse: make(map[net.Port]bool),
|
||||
mux: mux.NewServer(ctx),
|
||||
v: v,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse stream settings").Base(err).AtWarning()
|
||||
}
|
||||
if receiverConfig.ReceiveOriginalDestination {
|
||||
if mss.SocketSettings == nil {
|
||||
mss.SocketSettings = &internet.SocketConfig{}
|
||||
}
|
||||
if mss.SocketSettings.Tproxy == internet.SocketConfig_Off {
|
||||
mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect
|
||||
}
|
||||
mss.SocketSettings.ReceiveOriginalDestAddress = true
|
||||
}
|
||||
|
||||
h.streamSettings = mss
|
||||
|
||||
h.task = &task.Periodic{
|
||||
Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()),
|
||||
Execute: h.refresh,
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) allocatePort() net.Port {
|
||||
from := int(h.receiverConfig.PortRange.From)
|
||||
delta := int(h.receiverConfig.PortRange.To) - from + 1
|
||||
|
||||
h.portMutex.Lock()
|
||||
defer h.portMutex.Unlock()
|
||||
|
||||
for {
|
||||
r := dice.Roll(delta)
|
||||
port := net.Port(from + r)
|
||||
_, used := h.portsInUse[port]
|
||||
if !used {
|
||||
h.portsInUse[port] = true
|
||||
return port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) closeWorkers(workers []worker) {
|
||||
ports2Del := make([]net.Port, len(workers))
|
||||
for idx, worker := range workers {
|
||||
ports2Del[idx] = worker.Port()
|
||||
if err := worker.Close(); err != nil {
|
||||
newError("failed to close worker").Base(err).WriteToLog()
|
||||
}
|
||||
}
|
||||
|
||||
h.portMutex.Lock()
|
||||
for _, port := range ports2Del {
|
||||
delete(h.portsInUse, port)
|
||||
}
|
||||
h.portMutex.Unlock()
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) refresh() error {
|
||||
h.lastRefresh = time.Now()
|
||||
|
||||
timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2
|
||||
concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue()
|
||||
workers := make([]worker, 0, concurrency)
|
||||
|
||||
address := h.receiverConfig.Listen.AsAddress()
|
||||
if address == nil {
|
||||
address = net.AnyIP
|
||||
}
|
||||
|
||||
uplinkCounter, downlinkCounter := getStatCounter(h.v, h.tag)
|
||||
|
||||
for i := uint32(0); i < concurrency; i++ {
|
||||
port := h.allocatePort()
|
||||
rawProxy, err := core.CreateObject(h.v, h.proxyConfig)
|
||||
if err != nil {
|
||||
newError("failed to create proxy instance").Base(err).AtWarning().WriteToLog()
|
||||
continue
|
||||
}
|
||||
p := rawProxy.(proxy.Inbound)
|
||||
nl := p.Network()
|
||||
if net.HasNetwork(nl, net.Network_TCP) {
|
||||
worker := &tcpWorker{
|
||||
tag: h.tag,
|
||||
address: address,
|
||||
port: port,
|
||||
proxy: p,
|
||||
stream: h.streamSettings,
|
||||
recvOrigDest: h.receiverConfig.ReceiveOriginalDestination,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: h.receiverConfig.GetEffectiveSniffingSettings(),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: h.ctx,
|
||||
}
|
||||
if err := worker.Start(); err != nil {
|
||||
newError("failed to create TCP worker").Base(err).AtWarning().WriteToLog()
|
||||
continue
|
||||
}
|
||||
workers = append(workers, worker)
|
||||
}
|
||||
|
||||
if net.HasNetwork(nl, net.Network_UDP) {
|
||||
worker := &udpWorker{
|
||||
tag: h.tag,
|
||||
proxy: p,
|
||||
address: address,
|
||||
port: port,
|
||||
dispatcher: h.mux,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
stream: h.streamSettings,
|
||||
}
|
||||
if err := worker.Start(); err != nil {
|
||||
newError("failed to create UDP worker").Base(err).AtWarning().WriteToLog()
|
||||
continue
|
||||
}
|
||||
workers = append(workers, worker)
|
||||
}
|
||||
}
|
||||
|
||||
h.workerMutex.Lock()
|
||||
h.worker = workers
|
||||
h.workerMutex.Unlock()
|
||||
|
||||
time.AfterFunc(timeout, func() {
|
||||
h.closeWorkers(workers)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) Start() error {
|
||||
return h.task.Start()
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) Close() error {
|
||||
return h.task.Close()
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
|
||||
h.workerMutex.RLock()
|
||||
defer h.workerMutex.RUnlock()
|
||||
|
||||
if len(h.worker) == 0 {
|
||||
return nil, 0, 0
|
||||
}
|
||||
w := h.worker[dice.Roll(len(h.worker))]
|
||||
expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
|
||||
return w.Proxy(), w.Port(), int(expire)
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) Tag() string {
|
||||
return h.tag
|
||||
}
|
9
app/proxyman/inbound/errors.generated.go
Normal file
9
app/proxyman/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{})
|
||||
}
|
178
app/proxyman/inbound/inbound.go
Normal file
178
app/proxyman/inbound/inbound.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package inbound
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/proxyman"
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/serial"
|
||||
"github.com/xtls/xray-core/v1/common/session"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/features/inbound"
|
||||
)
|
||||
|
||||
// Manager is to manage all inbound handlers.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
untaggedHandler []inbound.Handler
|
||||
taggedHandlers map[string]inbound.Handler
|
||||
running bool
|
||||
}
|
||||
|
||||
// New returns a new Manager for inbound handlers.
|
||||
func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) {
|
||||
m := &Manager{
|
||||
taggedHandlers: make(map[string]inbound.Handler),
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Type implements common.HasType.
|
||||
func (*Manager) Type() interface{} {
|
||||
return inbound.ManagerType()
|
||||
}
|
||||
|
||||
// AddHandler implements inbound.Manager.
|
||||
func (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
tag := handler.Tag()
|
||||
if len(tag) > 0 {
|
||||
m.taggedHandlers[tag] = handler
|
||||
} else {
|
||||
m.untaggedHandler = append(m.untaggedHandler, handler)
|
||||
}
|
||||
|
||||
if m.running {
|
||||
return handler.Start()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHandler implements inbound.Manager.
|
||||
func (m *Manager) GetHandler(ctx context.Context, tag string) (inbound.Handler, error) {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
handler, found := m.taggedHandlers[tag]
|
||||
if !found {
|
||||
return nil, newError("handler not found: ", tag)
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
// RemoveHandler implements inbound.Manager.
|
||||
func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
|
||||
if tag == "" {
|
||||
return common.ErrNoClue
|
||||
}
|
||||
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
if handler, found := m.taggedHandlers[tag]; found {
|
||||
if err := handler.Close(); err != nil {
|
||||
newError("failed to close handler ", tag).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
delete(m.taggedHandlers, tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
return common.ErrNoClue
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (m *Manager) Start() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
m.running = true
|
||||
|
||||
for _, handler := range m.taggedHandlers {
|
||||
if err := handler.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, handler := range m.untaggedHandler {
|
||||
if err := handler.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
m.running = false
|
||||
|
||||
var errors []interface{}
|
||||
for _, handler := range m.taggedHandlers {
|
||||
if err := handler.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
for _, handler := range m.untaggedHandler {
|
||||
if err := handler.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all handlers").Base(newError(serial.Concat(errors...)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewHandler creates a new inbound.Handler based on the given config.
|
||||
func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound.Handler, error) {
|
||||
rawReceiverSettings, err := config.ReceiverSettings.GetInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxySettings, err := config.ProxySettings.GetInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag := config.Tag
|
||||
|
||||
receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
|
||||
if !ok {
|
||||
return nil, newError("not a ReceiverConfig").AtError()
|
||||
}
|
||||
|
||||
streamSettings := receiverSettings.StreamSettings
|
||||
if streamSettings != nil && streamSettings.SocketSettings != nil {
|
||||
ctx = session.ContextWithSockopt(ctx, &session.Sockopt{
|
||||
Mark: streamSettings.SocketSettings.Mark,
|
||||
})
|
||||
}
|
||||
|
||||
allocStrategy := receiverSettings.AllocationStrategy
|
||||
if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always {
|
||||
return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
|
||||
}
|
||||
|
||||
if allocStrategy.Type == proxyman.AllocationStrategy_Random {
|
||||
return NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings)
|
||||
}
|
||||
return nil, newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError()
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return New(ctx, config.(*proxyman.InboundConfig))
|
||||
}))
|
||||
common.Must(common.RegisterConfig((*core.InboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return NewHandler(ctx, config.(*core.InboundHandlerConfig))
|
||||
}))
|
||||
}
|
483
app/proxyman/inbound/worker.go
Normal file
483
app/proxyman/inbound/worker.go
Normal file
|
@ -0,0 +1,483 @@
|
|||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/proxyman"
|
||||
"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/serial"
|
||||
"github.com/xtls/xray-core/v1/common/session"
|
||||
"github.com/xtls/xray-core/v1/common/signal/done"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
"github.com/xtls/xray-core/v1/features/routing"
|
||||
"github.com/xtls/xray-core/v1/features/stats"
|
||||
"github.com/xtls/xray-core/v1/proxy"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/tcp"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/udp"
|
||||
"github.com/xtls/xray-core/v1/transport/pipe"
|
||||
)
|
||||
|
||||
type worker interface {
|
||||
Start() error
|
||||
Close() error
|
||||
Port() net.Port
|
||||
Proxy() proxy.Inbound
|
||||
}
|
||||
|
||||
type tcpWorker struct {
|
||||
address net.Address
|
||||
port net.Port
|
||||
proxy proxy.Inbound
|
||||
stream *internet.MemoryStreamConfig
|
||||
recvOrigDest bool
|
||||
tag string
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
hub internet.Listener
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func getTProxyType(s *internet.MemoryStreamConfig) internet.SocketConfig_TProxyMode {
|
||||
if s == nil || s.SocketSettings == nil {
|
||||
return internet.SocketConfig_Off
|
||||
}
|
||||
return s.SocketSettings.Tproxy
|
||||
}
|
||||
|
||||
func (w *tcpWorker) callback(conn internet.Connection) {
|
||||
ctx, cancel := context.WithCancel(w.ctx)
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
|
||||
if w.recvOrigDest {
|
||||
var dest net.Destination
|
||||
switch getTProxyType(w.stream) {
|
||||
case internet.SocketConfig_Redirect:
|
||||
d, err := tcp.GetOriginalDestination(conn)
|
||||
if err != nil {
|
||||
newError("failed to get original destination").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
} else {
|
||||
dest = d
|
||||
}
|
||||
case internet.SocketConfig_TProxy:
|
||||
dest = net.DestinationFromAddr(conn.LocalAddr())
|
||||
}
|
||||
if dest.IsValid() {
|
||||
ctx = session.ContextWithOutbound(ctx, &session.Outbound{
|
||||
Target: dest,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: net.DestinationFromAddr(conn.RemoteAddr()),
|
||||
Gateway: net.TCPDestination(w.address, w.port),
|
||||
Tag: w.tag,
|
||||
})
|
||||
content := new(session.Content)
|
||||
if w.sniffingConfig != nil {
|
||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
||||
conn = &internet.StatCouterConnection{
|
||||
Connection: conn,
|
||||
ReadCounter: w.uplinkCounter,
|
||||
WriteCounter: w.downlinkCounter,
|
||||
}
|
||||
}
|
||||
if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {
|
||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
cancel()
|
||||
if err := conn.Close(); err != nil {
|
||||
newError("failed to close connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *tcpWorker) Proxy() proxy.Inbound {
|
||||
return w.proxy
|
||||
}
|
||||
|
||||
func (w *tcpWorker) Start() error {
|
||||
ctx := context.Background()
|
||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn internet.Connection) {
|
||||
go w.callback(conn)
|
||||
})
|
||||
if err != nil {
|
||||
return newError("failed to listen TCP on ", w.port).AtWarning().Base(err)
|
||||
}
|
||||
w.hub = hub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *tcpWorker) Close() error {
|
||||
var errors []interface{}
|
||||
if w.hub != nil {
|
||||
if err := common.Close(w.hub); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if err := common.Close(w.proxy); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all resources").Base(newError(serial.Concat(errors...)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *tcpWorker) Port() net.Port {
|
||||
return w.port
|
||||
}
|
||||
|
||||
type udpConn struct {
|
||||
lastActivityTime int64 // in seconds
|
||||
reader buf.Reader
|
||||
writer buf.Writer
|
||||
output func([]byte) (int, error)
|
||||
remote net.Addr
|
||||
local net.Addr
|
||||
done *done.Instance
|
||||
uplink stats.Counter
|
||||
downlink stats.Counter
|
||||
}
|
||||
|
||||
func (c *udpConn) updateActivity() {
|
||||
atomic.StoreInt64(&c.lastActivityTime, time.Now().Unix())
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements buf.Reader
|
||||
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
mb, err := c.reader.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.updateActivity()
|
||||
|
||||
if c.uplink != nil {
|
||||
c.uplink.Add(int64(mb.Len()))
|
||||
}
|
||||
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(buf []byte) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (c *udpConn) Write(buf []byte) (int, error) {
|
||||
n, err := c.output(buf)
|
||||
if c.downlink != nil {
|
||||
c.downlink.Add(int64(n))
|
||||
}
|
||||
if err == nil {
|
||||
c.updateActivity()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *udpConn) Close() error {
|
||||
common.Must(c.done.Close())
|
||||
common.Must(common.Close(c.writer))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpConn) RemoteAddr() net.Addr {
|
||||
return c.remote
|
||||
}
|
||||
|
||||
func (c *udpConn) LocalAddr() net.Addr {
|
||||
return c.local
|
||||
}
|
||||
|
||||
func (*udpConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*udpConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*udpConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type connID struct {
|
||||
src net.Destination
|
||||
dest net.Destination
|
||||
}
|
||||
|
||||
type udpWorker struct {
|
||||
sync.RWMutex
|
||||
|
||||
proxy proxy.Inbound
|
||||
hub *udp.Hub
|
||||
address net.Address
|
||||
port net.Port
|
||||
tag string
|
||||
stream *internet.MemoryStreamConfig
|
||||
dispatcher routing.Dispatcher
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
checker *task.Periodic
|
||||
activeConn map[connID]*udpConn
|
||||
}
|
||||
|
||||
func (w *udpWorker) getConnection(id connID) (*udpConn, bool) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if conn, found := w.activeConn[id]; found && !conn.done.Done() {
|
||||
return conn, true
|
||||
}
|
||||
|
||||
pReader, pWriter := pipe.New(pipe.DiscardOverflow(), pipe.WithSizeLimit(16*1024))
|
||||
conn := &udpConn{
|
||||
reader: pReader,
|
||||
writer: pWriter,
|
||||
output: func(b []byte) (int, error) {
|
||||
return w.hub.WriteTo(b, id.src)
|
||||
},
|
||||
remote: &net.UDPAddr{
|
||||
IP: id.src.Address.IP(),
|
||||
Port: int(id.src.Port),
|
||||
},
|
||||
local: &net.UDPAddr{
|
||||
IP: w.address.IP(),
|
||||
Port: int(w.port),
|
||||
},
|
||||
done: done.New(),
|
||||
uplink: w.uplinkCounter,
|
||||
downlink: w.downlinkCounter,
|
||||
}
|
||||
w.activeConn[id] = conn
|
||||
|
||||
conn.updateActivity()
|
||||
return conn, false
|
||||
}
|
||||
|
||||
func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) {
|
||||
id := connID{
|
||||
src: source,
|
||||
}
|
||||
if originalDest.IsValid() {
|
||||
id.dest = originalDest
|
||||
}
|
||||
conn, existing := w.getConnection(id)
|
||||
|
||||
// payload will be discarded in pipe is full.
|
||||
conn.writer.WriteMultiBuffer(buf.MultiBuffer{b})
|
||||
|
||||
if !existing {
|
||||
common.Must(w.checker.Start())
|
||||
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
|
||||
if originalDest.IsValid() {
|
||||
ctx = session.ContextWithOutbound(ctx, &session.Outbound{
|
||||
Target: originalDest,
|
||||
})
|
||||
}
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: source,
|
||||
Gateway: net.UDPDestination(w.address, w.port),
|
||||
Tag: w.tag,
|
||||
})
|
||||
if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {
|
||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
conn.Close()
|
||||
w.removeConn(id)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *udpWorker) removeConn(id connID) {
|
||||
w.Lock()
|
||||
delete(w.activeConn, id)
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *udpWorker) handlePackets() {
|
||||
receive := w.hub.Receive()
|
||||
for payload := range receive {
|
||||
w.callback(payload.Payload, payload.Source, payload.Target)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *udpWorker) clean() error {
|
||||
nowSec := time.Now().Unix()
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if len(w.activeConn) == 0 {
|
||||
return newError("no more connections. stopping...")
|
||||
}
|
||||
|
||||
for addr, conn := range w.activeConn {
|
||||
if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 8 { // TODO Timeout too small
|
||||
delete(w.activeConn, addr)
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if len(w.activeConn) == 0 {
|
||||
w.activeConn = make(map[connID]*udpConn, 16)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *udpWorker) Start() error {
|
||||
w.activeConn = make(map[connID]*udpConn, 16)
|
||||
ctx := context.Background()
|
||||
h, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.checker = &task.Periodic{
|
||||
Interval: time.Second * 16,
|
||||
Execute: w.clean,
|
||||
}
|
||||
|
||||
w.hub = h
|
||||
go w.handlePackets()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *udpWorker) Close() error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
var errors []interface{}
|
||||
|
||||
if w.hub != nil {
|
||||
if err := w.hub.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if w.checker != nil {
|
||||
if err := w.checker.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := common.Close(w.proxy); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all resources").Base(newError(serial.Concat(errors...)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *udpWorker) Port() net.Port {
|
||||
return w.port
|
||||
}
|
||||
|
||||
func (w *udpWorker) Proxy() proxy.Inbound {
|
||||
return w.proxy
|
||||
}
|
||||
|
||||
type dsWorker struct {
|
||||
address net.Address
|
||||
proxy proxy.Inbound
|
||||
stream *internet.MemoryStreamConfig
|
||||
tag string
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
hub internet.Listener
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (w *dsWorker) callback(conn internet.Connection) {
|
||||
ctx, cancel := context.WithCancel(w.ctx)
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: net.DestinationFromAddr(conn.RemoteAddr()),
|
||||
Gateway: net.UnixDestination(w.address),
|
||||
Tag: w.tag,
|
||||
})
|
||||
content := new(session.Content)
|
||||
if w.sniffingConfig != nil {
|
||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
||||
conn = &internet.StatCouterConnection{
|
||||
Connection: conn,
|
||||
ReadCounter: w.uplinkCounter,
|
||||
WriteCounter: w.downlinkCounter,
|
||||
}
|
||||
}
|
||||
if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil {
|
||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
cancel()
|
||||
if err := conn.Close(); err != nil {
|
||||
newError("failed to close connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *dsWorker) Proxy() proxy.Inbound {
|
||||
return w.proxy
|
||||
}
|
||||
|
||||
func (w *dsWorker) Port() net.Port {
|
||||
return net.Port(0)
|
||||
}
|
||||
func (w *dsWorker) Start() error {
|
||||
ctx := context.Background()
|
||||
hub, err := internet.ListenUnix(ctx, w.address, w.stream, func(conn internet.Connection) {
|
||||
go w.callback(conn)
|
||||
})
|
||||
if err != nil {
|
||||
return newError("failed to listen Unix Domain Socket on ", w.address).AtWarning().Base(err)
|
||||
}
|
||||
w.hub = hub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *dsWorker) Close() error {
|
||||
var errors []interface{}
|
||||
if w.hub != nil {
|
||||
if err := common.Close(w.hub); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if err := common.Close(w.proxy); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all resources").Base(newError(serial.Concat(errors...)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
9
app/proxyman/outbound/errors.generated.go
Normal file
9
app/proxyman/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{})
|
||||
}
|
228
app/proxyman/outbound/handler.go
Normal file
228
app/proxyman/outbound/handler.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/proxyman"
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/mux"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"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/stats"
|
||||
"github.com/xtls/xray-core/v1/proxy"
|
||||
"github.com/xtls/xray-core/v1/transport"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/tls"
|
||||
"github.com/xtls/xray-core/v1/transport/pipe"
|
||||
)
|
||||
|
||||
func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
|
||||
var uplinkCounter stats.Counter
|
||||
var downlinkCounter stats.Counter
|
||||
|
||||
policy := v.GetFeature(policy.ManagerType()).(policy.Manager)
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
uplinkCounter = c
|
||||
}
|
||||
}
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
}
|
||||
|
||||
return uplinkCounter, downlinkCounter
|
||||
}
|
||||
|
||||
// Handler is an implements of outbound.Handler.
|
||||
type Handler struct {
|
||||
tag string
|
||||
senderSettings *proxyman.SenderConfig
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
proxy proxy.Outbound
|
||||
outboundManager outbound.Manager
|
||||
mux *mux.ClientManager
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
}
|
||||
|
||||
// NewHandler create a new Handler based on the given configuration.
|
||||
func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbound.Handler, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
||||
h := &Handler{
|
||||
tag: config.Tag,
|
||||
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
}
|
||||
|
||||
if config.SenderSettings != nil {
|
||||
senderSettings, err := config.SenderSettings.GetInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch s := senderSettings.(type) {
|
||||
case *proxyman.SenderConfig:
|
||||
h.senderSettings = s
|
||||
mss, err := internet.ToMemoryStreamConfig(s.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse stream settings").Base(err).AtWarning()
|
||||
}
|
||||
h.streamSettings = mss
|
||||
default:
|
||||
return nil, newError("settings is not SenderConfig")
|
||||
}
|
||||
}
|
||||
|
||||
proxyConfig, err := config.ProxySettings.GetInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyHandler, ok := rawProxyHandler.(proxy.Outbound)
|
||||
if !ok {
|
||||
return nil, newError("not an outbound handler")
|
||||
}
|
||||
|
||||
if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil {
|
||||
config := h.senderSettings.MultiplexSettings
|
||||
if config.Concurrency < 1 || config.Concurrency > 1024 {
|
||||
return nil, newError("invalid mux concurrency: ", config.Concurrency).AtWarning()
|
||||
}
|
||||
h.mux = &mux.ClientManager{
|
||||
Enabled: h.senderSettings.MultiplexSettings.Enabled,
|
||||
Picker: &mux.IncrementalWorkerPicker{
|
||||
Factory: &mux.DialingWorkerFactory{
|
||||
Proxy: proxyHandler,
|
||||
Dialer: h,
|
||||
Strategy: mux.ClientStrategy{
|
||||
MaxConcurrency: config.Concurrency,
|
||||
MaxConnection: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
h.proxy = proxyHandler
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Tag implements outbound.Handler.
|
||||
func (h *Handler) Tag() string {
|
||||
return h.tag
|
||||
}
|
||||
|
||||
// Dispatch implements proxy.Outbound.Dispatch.
|
||||
func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
||||
if h.mux != nil && (h.mux.Enabled || session.MuxPreferedFromContext(ctx)) {
|
||||
if err := h.mux.Dispatch(ctx, link); err != nil {
|
||||
newError("failed to process mux outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
common.Interrupt(link.Writer)
|
||||
}
|
||||
} else {
|
||||
if err := h.proxy.Process(ctx, link, h); err != nil {
|
||||
// Ensure outbound ray is properly closed.
|
||||
newError("failed to process outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
common.Interrupt(link.Writer)
|
||||
} else {
|
||||
common.Must(common.Close(link.Writer))
|
||||
}
|
||||
common.Interrupt(link.Reader)
|
||||
}
|
||||
}
|
||||
|
||||
// Address implements internet.Dialer.
|
||||
func (h *Handler) Address() net.Address {
|
||||
if h.senderSettings == nil || h.senderSettings.Via == nil {
|
||||
return nil
|
||||
}
|
||||
return h.senderSettings.Via.AsAddress()
|
||||
}
|
||||
|
||||
// Dial implements internet.Dialer.
|
||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) {
|
||||
if h.senderSettings != nil {
|
||||
if h.senderSettings.ProxySettings.HasTag() {
|
||||
tag := h.senderSettings.ProxySettings.Tag
|
||||
handler := h.outboundManager.GetHandler(tag)
|
||||
if handler != nil {
|
||||
newError("proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||
ctx = session.ContextWithOutbound(ctx, &session.Outbound{
|
||||
Target: dest,
|
||||
})
|
||||
|
||||
opts := pipe.OptionsFromContext(ctx)
|
||||
uplinkReader, uplinkWriter := pipe.New(opts...)
|
||||
downlinkReader, downlinkWriter := pipe.New(opts...)
|
||||
|
||||
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
|
||||
conn := net.NewConnection(net.ConnectionInputMulti(uplinkWriter), net.ConnectionOutputMulti(downlinkReader))
|
||||
|
||||
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
|
||||
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
|
||||
conn = tls.Client(conn, tlsConfig)
|
||||
}
|
||||
|
||||
return h.getStatCouterConnection(conn), nil
|
||||
}
|
||||
|
||||
newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
|
||||
if h.senderSettings.Via != nil {
|
||||
outbound := session.OutboundFromContext(ctx)
|
||||
if outbound == nil {
|
||||
outbound = new(session.Outbound)
|
||||
ctx = session.ContextWithOutbound(ctx, outbound)
|
||||
}
|
||||
outbound.Gateway = h.senderSettings.Via.AsAddress()
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
||||
return h.getStatCouterConnection(conn), err
|
||||
}
|
||||
|
||||
func (h *Handler) getStatCouterConnection(conn internet.Connection) internet.Connection {
|
||||
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
||||
return &internet.StatCouterConnection{
|
||||
Connection: conn,
|
||||
ReadCounter: h.downlinkCounter,
|
||||
WriteCounter: h.uplinkCounter,
|
||||
}
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
// GetOutbound implements proxy.GetOutbound.
|
||||
func (h *Handler) GetOutbound() proxy.Outbound {
|
||||
return h.proxy
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (h *Handler) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (h *Handler) Close() error {
|
||||
common.Close(h.mux)
|
||||
return nil
|
||||
}
|
80
app/proxyman/outbound/handler_test.go
Normal file
80
app/proxyman/outbound/handler_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package outbound_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/policy"
|
||||
. "github.com/xtls/xray-core/v1/app/proxyman/outbound"
|
||||
"github.com/xtls/xray-core/v1/app/stats"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/common/serial"
|
||||
core "github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/features/outbound"
|
||||
"github.com/xtls/xray-core/v1/proxy/freedom"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
)
|
||||
|
||||
func TestInterfaces(t *testing.T) {
|
||||
_ = (outbound.Handler)(new(Handler))
|
||||
_ = (outbound.Manager)(new(Manager))
|
||||
}
|
||||
|
||||
const xrayKey core.XrayKey = 1
|
||||
|
||||
func TestOutboundWithoutStatCounter(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&stats.Config{}),
|
||||
serial.ToTypedMessage(&policy.Config{
|
||||
System: &policy.SystemPolicy{
|
||||
Stats: &policy.SystemPolicy_Stats{
|
||||
InboundUplink: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
v, _ := core.New(config)
|
||||
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||
Tag: "tag",
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
})
|
||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||
_, ok := conn.(*internet.StatCouterConnection)
|
||||
if ok {
|
||||
t.Errorf("Expected conn to not be StatCouterConnection")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutboundWithStatCounter(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&stats.Config{}),
|
||||
serial.ToTypedMessage(&policy.Config{
|
||||
System: &policy.SystemPolicy{
|
||||
Stats: &policy.SystemPolicy_Stats{
|
||||
OutboundUplink: true,
|
||||
OutboundDownlink: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
v, _ := core.New(config)
|
||||
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||
Tag: "tag",
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
})
|
||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||
_, ok := conn.(*internet.StatCouterConnection)
|
||||
if !ok {
|
||||
t.Errorf("Expected conn to be StatCouterConnection")
|
||||
}
|
||||
}
|
170
app/proxyman/outbound/outbound.go
Normal file
170
app/proxyman/outbound/outbound.go
Normal file
|
@ -0,0 +1,170 @@
|
|||
package outbound
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/v1/app/proxyman"
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/errors"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/features/outbound"
|
||||
)
|
||||
|
||||
// Manager is to manage all outbound handlers.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
defaultHandler outbound.Handler
|
||||
taggedHandler map[string]outbound.Handler
|
||||
untaggedHandlers []outbound.Handler
|
||||
running bool
|
||||
}
|
||||
|
||||
// New creates a new Manager.
|
||||
func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) {
|
||||
m := &Manager{
|
||||
taggedHandler: make(map[string]outbound.Handler),
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Type implements common.HasType.
|
||||
func (m *Manager) Type() interface{} {
|
||||
return outbound.ManagerType()
|
||||
}
|
||||
|
||||
// Start implements core.Feature
|
||||
func (m *Manager) Start() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
m.running = true
|
||||
|
||||
for _, h := range m.taggedHandler {
|
||||
if err := h.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, h := range m.untaggedHandlers {
|
||||
if err := h.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements core.Feature
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
m.running = false
|
||||
|
||||
var errs []error
|
||||
for _, h := range m.taggedHandler {
|
||||
errs = append(errs, h.Close())
|
||||
}
|
||||
|
||||
for _, h := range m.untaggedHandlers {
|
||||
errs = append(errs, h.Close())
|
||||
}
|
||||
|
||||
return errors.Combine(errs...)
|
||||
}
|
||||
|
||||
// GetDefaultHandler implements outbound.Manager.
|
||||
func (m *Manager) GetDefaultHandler() outbound.Handler {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
if m.defaultHandler == nil {
|
||||
return nil
|
||||
}
|
||||
return m.defaultHandler
|
||||
}
|
||||
|
||||
// GetHandler implements outbound.Manager.
|
||||
func (m *Manager) GetHandler(tag string) outbound.Handler {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
if handler, found := m.taggedHandler[tag]; found {
|
||||
return handler
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddHandler implements outbound.Manager.
|
||||
func (m *Manager) AddHandler(ctx context.Context, handler outbound.Handler) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
if m.defaultHandler == nil {
|
||||
m.defaultHandler = handler
|
||||
}
|
||||
|
||||
tag := handler.Tag()
|
||||
if len(tag) > 0 {
|
||||
m.taggedHandler[tag] = handler
|
||||
} else {
|
||||
m.untaggedHandlers = append(m.untaggedHandlers, handler)
|
||||
}
|
||||
|
||||
if m.running {
|
||||
return handler.Start()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveHandler implements outbound.Manager.
|
||||
func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
|
||||
if tag == "" {
|
||||
return common.ErrNoClue
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
delete(m.taggedHandler, tag)
|
||||
if m.defaultHandler != nil && m.defaultHandler.Tag() == tag {
|
||||
m.defaultHandler = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select implements outbound.HandlerSelector.
|
||||
func (m *Manager) Select(selectors []string) []string {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
tags := make([]string, 0, len(selectors))
|
||||
|
||||
for tag := range m.taggedHandler {
|
||||
match := false
|
||||
for _, selector := range selectors {
|
||||
if strings.HasPrefix(tag, selector) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return New(ctx, config.(*proxyman.OutboundConfig))
|
||||
}))
|
||||
common.Must(common.RegisterConfig((*core.OutboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return NewHandler(ctx, config.(*core.OutboundHandlerConfig))
|
||||
}))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue