Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport

This commit is contained in:
Sergei Maklagin
2026-02-26 22:44:31 +03:00
parent 287fe834db
commit c0aa3480c5
115 changed files with 12582 additions and 301 deletions

View File

@@ -0,0 +1,274 @@
package client
import (
"context"
"net"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
CM "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/node_manager/manager"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
func RegisterService(registry *boxService.Registry) {
boxService.Register[option.NodeManagerClientServiceOptions](registry, C.TypeNodeManagerClient, NewService)
}
type Service struct {
boxService.Adapter
ctx context.Context
logger log.ContextLogger
dialer N.Dialer
creds credentials.TransportCredentials
options option.NodeManagerClientServiceOptions
conn *grpc.ClientConn
mtx sync.Mutex
}
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerClientServiceOptions) (adapter.Service, error) {
outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
if err != nil {
return nil, err
}
creds := insecure.NewCredentials()
if options.TLS != nil {
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
creds = &tlsCreds{tlsConfig}
}
return &Service{
Adapter: boxService.NewAdapter(C.TypeManager, tag),
ctx: ctx,
logger: logger,
dialer: outboundDialer,
creds: creds,
options: options,
}, nil
}
func (s *Service) AddNode(uuid string, node CM.ConnectedNode) error {
go func() {
isRetry := false
for {
if !isRetry {
select {
case <-s.ctx.Done():
return
default:
isRetry = true
}
} else {
select {
case <-time.After(5 * time.Second):
break
case <-s.ctx.Done():
return
}
}
conn, err := s.getConn()
if err != nil {
s.logger.Error(err)
continue
}
client := pb.NewManagerClient(conn)
stream, err := client.AddNode(s.ctx, &pb.Node{Uuid: uuid})
if err != nil {
s.logger.Error(err)
continue
}
err = s.handler(node, stream)
if err != nil {
s.logger.Error(err)
continue
}
}
}()
return nil
}
func (s *Service) AcquireLock(limiterId int, id string) (string, error) {
conn, err := s.getConn()
if err != nil {
return "", err
}
client := pb.NewManagerClient(conn)
lockReply, err := client.AcquireLock(s.ctx, &pb.AcquireLockRequest{LimiterId: int32(limiterId), Id: id})
if err != nil {
return "", err
}
return lockReply.HandleId, err
}
func (s *Service) RefreshLock(limiterId int, id string, handleId string) error {
conn, err := s.getConn()
if err != nil {
return err
}
client := pb.NewManagerClient(conn)
_, err = client.RefreshLock(s.ctx, &pb.LockData{LimiterId: int32(limiterId), Id: id, HandleId: handleId})
return err
}
func (s *Service) ReleaseLock(limiterId int, id string, handleId string) error {
conn, err := s.getConn()
if err != nil {
return err
}
client := pb.NewManagerClient(conn)
_, err = client.ReleaseLock(s.ctx, &pb.LockData{LimiterId: int32(limiterId), Id: id, HandleId: handleId})
return err
}
func (s *Service) Start(stage adapter.StartStage) error {
return nil
}
func (s *Service) Close() error {
return nil
}
func (s *Service) getConn() (*grpc.ClientConn, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.conn != nil {
state := s.conn.GetState()
if state != connectivity.Shutdown && state != connectivity.TransientFailure {
return s.conn, nil
}
}
for {
conn, err := s.createConn()
if err != nil {
return nil, err
}
s.conn = conn
return conn, nil
}
}
func (s *Service) createConn() (*grpc.ClientConn, error) {
conn, err := grpc.NewClient(
s.options.ServerOptions.Build().String(),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return s.dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(addr))
}),
grpc.WithTransportCredentials(s.creds),
)
if err != nil {
return nil, err
}
return conn, nil
}
func (s *Service) handler(node CM.ConnectedNode, stream grpc.ServerStreamingClient[pb.NodeData]) error {
for {
data, err := stream.Recv()
if err != nil {
return err
}
switch data.Op {
case pb.OpType_updateUser:
s.logger.DebugContext(s.ctx, "update user")
node.UpdateUser(s.convertUser(data.Data.(*pb.NodeData_User).User))
case pb.OpType_updateUsers:
s.logger.DebugContext(s.ctx, "update users")
users := data.Data.(*pb.NodeData_Users).Users.Values
convertedUsers := make([]CM.User, len(users))
for i, user := range users {
convertedUsers[i] = s.convertUser(user)
}
node.UpdateUsers(convertedUsers)
case pb.OpType_deleteUser:
s.logger.DebugContext(s.ctx, "delete user")
node.DeleteUser(s.convertUser(data.Data.(*pb.NodeData_User).User))
case pb.OpType_updateConnectionLimiter:
s.logger.DebugContext(s.ctx, "update connection limiter")
node.UpdateConnectionLimiter(s.convertConnectionLimiter(data.Data.(*pb.NodeData_ConnectionLimiter).ConnectionLimiter))
case pb.OpType_updateConnectionLimiters:
s.logger.DebugContext(s.ctx, "update connection limiters")
limiters := data.Data.(*pb.NodeData_ConnectionLimiters).ConnectionLimiters.Values
convertedLimiters := make([]CM.ConnectionLimiter, len(limiters))
for i, limiter := range limiters {
convertedLimiters[i] = s.convertConnectionLimiter(limiter)
}
node.UpdateConnectionLimiters(convertedLimiters)
case pb.OpType_deleteConnectionLimiter:
s.logger.DebugContext(s.ctx, "delete connection limiter")
node.DeleteConnectionLimiter(s.convertConnectionLimiter(data.Data.(*pb.NodeData_ConnectionLimiter).ConnectionLimiter))
case pb.OpType_updateBandwidthLimiter:
s.logger.DebugContext(s.ctx, "update bandwidth limiter")
node.UpdateBandwidthLimiter(s.convertBandwidthLimiter(data.Data.(*pb.NodeData_BandwidthLimiter).BandwidthLimiter))
case pb.OpType_updateBandwidthLimiters:
s.logger.DebugContext(s.ctx, "update bandwidth limiters")
limiters := data.Data.(*pb.NodeData_BandwidthLimiters).BandwidthLimiters.Values
convertedLimiters := make([]CM.BandwidthLimiter, len(limiters))
for i, limiter := range limiters {
convertedLimiters[i] = s.convertBandwidthLimiter(limiter)
}
node.UpdateBandwidthLimiters(convertedLimiters)
case pb.OpType_deleteBandwidthLimiter:
s.logger.DebugContext(s.ctx, "delete bandwidth limiter")
node.DeleteBandwidthLimiter(s.convertBandwidthLimiter(data.Data.(*pb.NodeData_BandwidthLimiter).BandwidthLimiter))
}
}
}
func (s *Service) convertUser(user *pb.User) CM.User {
return CM.User{
ID: int(user.Id),
Username: user.Username,
Type: user.Type,
Inbound: user.Inbound,
UUID: user.Uuid,
Password: user.Password,
Flow: user.Flow,
AlterID: int(user.AlterId),
}
}
func (s *Service) convertBandwidthLimiter(limiter *pb.BandwidthLimiter) CM.BandwidthLimiter {
return CM.BandwidthLimiter{
ID: int(limiter.Id),
Username: limiter.Username,
Outbound: limiter.Outbound,
Strategy: limiter.Strategy,
Mode: limiter.Mode,
ConnectionType: limiter.ConnectionType,
Speed: limiter.Speed,
RawSpeed: limiter.RawSpeed,
}
}
func (s *Service) convertConnectionLimiter(limiter *pb.ConnectionLimiter) CM.ConnectionLimiter {
return CM.ConnectionLimiter{
ID: int(limiter.Id),
Username: limiter.Username,
Outbound: limiter.Outbound,
Strategy: limiter.Strategy,
ConnectionType: limiter.ConnectionType,
LockType: limiter.LockType,
Count: limiter.Count,
}
}

View File

@@ -0,0 +1,44 @@
package client
import (
"context"
"net"
"github.com/sagernet/sing-box/common/tls"
E "github.com/sagernet/sing/common/exceptions"
"google.golang.org/grpc/credentials"
)
type tlsCreds struct {
// TLS configuration
config tls.Config
}
func (c tlsCreds) Info() credentials.ProtocolInfo {
return credentials.ProtocolInfo{
SecurityProtocol: "tls",
SecurityVersion: "1.2",
ServerName: c.config.ServerName(),
}
}
func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
conn, err := tls.ClientHandshake(ctx, rawConn, c.config)
if err != nil {
return nil, nil, err
}
return conn, credentials.TLSInfo{State: conn.ConnectionState()}, err
}
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
return nil, nil, E.New("not implemented")
}
func (c *tlsCreds) Clone() credentials.TransportCredentials {
return &tlsCreds{config: c.config.Clone()}
}
func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
c.config.SetServerName(serverNameOverride)
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
syntax = "proto3";
option go_package = "github.com/sagernet/sing-box/service/remotemanager/manager";
package manager.v1;
service Manager {
rpc AddNode (Node) returns (stream NodeData);
rpc AcquireLock(AcquireLockRequest) returns (LockData);
rpc RefreshLock(LockData) returns (Empty);
rpc ReleaseLock(LockData) returns (Empty);
}
message Node {
string uuid = 1;
}
enum OpType {
updateUsers = 0;
updateUser = 1;
deleteUser = 2;
updateBandwidthLimiters = 3;
updateBandwidthLimiter = 4;
deleteBandwidthLimiter = 5;
updateConnectionLimiters = 6;
updateConnectionLimiter = 7;
deleteConnectionLimiter = 8;
}
message User {
int32 id = 1;
string username = 3;
string type = 4;
string inbound = 5;
string uuid = 6;
string password = 7;
string flow = 8;
int32 alter_id = 9;
}
message UserList {
repeated User values = 1;
}
message BandwidthLimiter {
int32 id = 1;
string username = 3;
string outbound = 4;
string strategy = 5;
string mode = 6;
string connection_type = 7;
string speed = 8;
uint64 raw_speed = 9;
}
message BandwidthLimiterList {
repeated BandwidthLimiter values = 1;
}
message ConnectionLimiter {
int32 id = 1;
string username = 3;
string outbound = 4;
string strategy = 5;
string connection_type = 6;
string lock_type = 7;
uint32 count = 8;
}
message ConnectionLimiterList {
repeated ConnectionLimiter values = 1;
}
message NodeData {
OpType op = 1;
oneof data {
UserList users = 2;
User user = 3;
BandwidthLimiterList bandwidth_limiters = 4;
BandwidthLimiter bandwidth_limiter = 5;
ConnectionLimiterList connection_limiters = 6;
ConnectionLimiter connection_limiter = 7;
}
}
message AcquireLockRequest {
int32 limiter_id = 1;
string id = 2;
}
message LockData {
int32 limiter_id = 1;
string id = 2;
string handleId = 3;
}
message Empty {
}

View File

@@ -0,0 +1,239 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// source: manager/manager.proto
package manager
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.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Manager_AddNode_FullMethodName = "/manager.v1.Manager/AddNode"
Manager_AcquireLock_FullMethodName = "/manager.v1.Manager/AcquireLock"
Manager_RefreshLock_FullMethodName = "/manager.v1.Manager/RefreshLock"
Manager_ReleaseLock_FullMethodName = "/manager.v1.Manager/ReleaseLock"
)
// ManagerClient is the client API for Manager 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 ManagerClient interface {
AddNode(ctx context.Context, in *Node, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NodeData], error)
AcquireLock(ctx context.Context, in *AcquireLockRequest, opts ...grpc.CallOption) (*LockData, error)
RefreshLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error)
ReleaseLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error)
}
type managerClient struct {
cc grpc.ClientConnInterface
}
func NewManagerClient(cc grpc.ClientConnInterface) ManagerClient {
return &managerClient{cc}
}
func (c *managerClient) AddNode(ctx context.Context, in *Node, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NodeData], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[0], Manager_AddNode_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[Node, NodeData]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Manager_AddNodeClient = grpc.ServerStreamingClient[NodeData]
func (c *managerClient) AcquireLock(ctx context.Context, in *AcquireLockRequest, opts ...grpc.CallOption) (*LockData, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LockData)
err := c.cc.Invoke(ctx, Manager_AcquireLock_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *managerClient) RefreshLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, Manager_RefreshLock_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *managerClient) ReleaseLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, Manager_ReleaseLock_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ManagerServer is the server API for Manager service.
// All implementations must embed UnimplementedManagerServer
// for forward compatibility.
type ManagerServer interface {
AddNode(*Node, grpc.ServerStreamingServer[NodeData]) error
AcquireLock(context.Context, *AcquireLockRequest) (*LockData, error)
RefreshLock(context.Context, *LockData) (*Empty, error)
ReleaseLock(context.Context, *LockData) (*Empty, error)
mustEmbedUnimplementedManagerServer()
}
// UnimplementedManagerServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedManagerServer struct{}
func (UnimplementedManagerServer) AddNode(*Node, grpc.ServerStreamingServer[NodeData]) error {
return status.Error(codes.Unimplemented, "method AddNode not implemented")
}
func (UnimplementedManagerServer) AcquireLock(context.Context, *AcquireLockRequest) (*LockData, error) {
return nil, status.Error(codes.Unimplemented, "method AcquireLock not implemented")
}
func (UnimplementedManagerServer) RefreshLock(context.Context, *LockData) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method RefreshLock not implemented")
}
func (UnimplementedManagerServer) ReleaseLock(context.Context, *LockData) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method ReleaseLock not implemented")
}
func (UnimplementedManagerServer) mustEmbedUnimplementedManagerServer() {}
func (UnimplementedManagerServer) testEmbeddedByValue() {}
// UnsafeManagerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ManagerServer will
// result in compilation errors.
type UnsafeManagerServer interface {
mustEmbedUnimplementedManagerServer()
}
func RegisterManagerServer(s grpc.ServiceRegistrar, srv ManagerServer) {
// If the following call panics, it indicates UnimplementedManagerServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Manager_ServiceDesc, srv)
}
func _Manager_AddNode_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(Node)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ManagerServer).AddNode(m, &grpc.GenericServerStream[Node, NodeData]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Manager_AddNodeServer = grpc.ServerStreamingServer[NodeData]
func _Manager_AcquireLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AcquireLockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ManagerServer).AcquireLock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Manager_AcquireLock_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagerServer).AcquireLock(ctx, req.(*AcquireLockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Manager_RefreshLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LockData)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ManagerServer).RefreshLock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Manager_RefreshLock_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagerServer).RefreshLock(ctx, req.(*LockData))
}
return interceptor(ctx, in, info, handler)
}
func _Manager_ReleaseLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LockData)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ManagerServer).ReleaseLock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Manager_ReleaseLock_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagerServer).ReleaseLock(ctx, req.(*LockData))
}
return interceptor(ctx, in, info, handler)
}
// Manager_ServiceDesc is the grpc.ServiceDesc for Manager service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Manager_ServiceDesc = grpc.ServiceDesc{
ServiceName: "manager.v1.Manager",
HandlerType: (*ManagerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "AcquireLock",
Handler: _Manager_AcquireLock_Handler,
},
{
MethodName: "RefreshLock",
Handler: _Manager_RefreshLock_Handler,
},
{
MethodName: "ReleaseLock",
Handler: _Manager_ReleaseLock_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "AddNode",
Handler: _Manager_AddNode_Handler,
ServerStreams: true,
},
},
Metadata: "manager/manager.proto",
}

View File

@@ -0,0 +1,203 @@
package server
import (
"context"
"sync"
"github.com/sagernet/sing-box/log"
CS "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/node_manager/manager"
E "github.com/sagernet/sing/common/exceptions"
"google.golang.org/grpc"
)
type RemoteNode struct {
ctx context.Context
logger log.ContextLogger
stream grpc.ServerStreamingServer[pb.NodeData]
errChan chan error
mtx sync.Mutex
}
func NewRemoteNode(ctx context.Context, logger log.ContextLogger, stream grpc.ServerStreamingServer[pb.NodeData]) (*RemoteNode, chan error) {
errChan := make(chan error)
return &RemoteNode{
ctx: ctx,
logger: logger,
stream: stream,
errChan: errChan,
}, errChan
}
func (s *RemoteNode) UpdateUser(user CS.User) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.send(&pb.NodeData{
Op: pb.OpType_updateUser,
Data: &pb.NodeData_User{User: s.convertUser(user)},
})
}
func (s *RemoteNode) UpdateUsers(users []CS.User) {
s.mtx.Lock()
defer s.mtx.Unlock()
pbUsers := make([]*pb.User, len(users))
for i, user := range users {
pbUsers[i] = s.convertUser(user)
}
s.send(&pb.NodeData{
Op: pb.OpType_updateUsers,
Data: &pb.NodeData_Users{Users: &pb.UserList{Values: pbUsers}},
})
}
func (s *RemoteNode) DeleteUser(user CS.User) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.send(&pb.NodeData{
Op: pb.OpType_deleteUser,
Data: &pb.NodeData_User{User: s.convertUser(user)},
})
}
func (s *RemoteNode) UpdateConnectionLimiter(limiter CS.ConnectionLimiter) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.send(&pb.NodeData{
Op: pb.OpType_updateConnectionLimiter,
Data: &pb.NodeData_ConnectionLimiter{ConnectionLimiter: s.convertConnectionLimiter(limiter)},
})
}
func (s *RemoteNode) UpdateConnectionLimiters(limiters []CS.ConnectionLimiter) {
s.mtx.Lock()
defer s.mtx.Unlock()
pbLimiters := make([]*pb.ConnectionLimiter, len(limiters))
for i, limiters := range limiters {
pbLimiters[i] = s.convertConnectionLimiter(limiters)
}
s.send(&pb.NodeData{
Op: pb.OpType_updateConnectionLimiters,
Data: &pb.NodeData_ConnectionLimiters{ConnectionLimiters: &pb.ConnectionLimiterList{Values: pbLimiters}},
})
}
func (s *RemoteNode) DeleteConnectionLimiter(limiter CS.ConnectionLimiter) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.send(&pb.NodeData{
Op: pb.OpType_deleteConnectionLimiter,
Data: &pb.NodeData_ConnectionLimiter{ConnectionLimiter: s.convertConnectionLimiter(limiter)},
})
}
func (s *RemoteNode) UpdateBandwidthLimiter(limiter CS.BandwidthLimiter) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.send(&pb.NodeData{
Op: pb.OpType_updateBandwidthLimiter,
Data: &pb.NodeData_BandwidthLimiter{BandwidthLimiter: s.convertBandwidthLimiter(limiter)},
})
}
func (s *RemoteNode) UpdateBandwidthLimiters(limiters []CS.BandwidthLimiter) {
s.mtx.Lock()
defer s.mtx.Unlock()
pbLimiters := make([]*pb.BandwidthLimiter, len(limiters))
for i, limiters := range limiters {
pbLimiters[i] = s.convertBandwidthLimiter(limiters)
}
s.send(&pb.NodeData{
Op: pb.OpType_updateBandwidthLimiters,
Data: &pb.NodeData_BandwidthLimiters{BandwidthLimiters: &pb.BandwidthLimiterList{Values: pbLimiters}},
})
}
func (s *RemoteNode) DeleteBandwidthLimiter(limiter CS.BandwidthLimiter) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.send(&pb.NodeData{
Op: pb.OpType_deleteBandwidthLimiter,
Data: &pb.NodeData_BandwidthLimiter{BandwidthLimiter: s.convertBandwidthLimiter(limiter)},
})
}
func (s *RemoteNode) IsLocal() bool {
return false
}
func (s *RemoteNode) IsOnline() bool {
s.mtx.Lock()
defer s.mtx.Unlock()
select {
case <-s.stream.Context().Done():
return false
default:
return true
}
}
func (s *RemoteNode) Close() error {
s.close(E.New("server connection is closed"))
return nil
}
func (s *RemoteNode) send(data *pb.NodeData) {
select {
case <-s.ctx.Done():
s.close(E.New("server connection is closed"))
return
case <-s.stream.Context().Done():
s.close(E.New("client connection is closed"))
return
default:
}
err := s.stream.Send(data)
if err != nil {
s.close(err)
}
}
func (s *RemoteNode) close(err error) {
s.errChan <- err
close(s.errChan)
}
func (s *RemoteNode) convertUser(user CS.User) *pb.User {
return &pb.User{
Id: int32(user.ID),
Username: user.Username,
Type: user.Type,
Inbound: user.Inbound,
Uuid: user.UUID,
Password: user.Password,
Flow: user.Flow,
AlterId: int32(user.AlterID),
}
}
func (s *RemoteNode) convertConnectionLimiter(limiter CS.ConnectionLimiter) *pb.ConnectionLimiter {
return &pb.ConnectionLimiter{
Id: int32(limiter.ID),
Username: limiter.Username,
Outbound: limiter.Outbound,
Strategy: limiter.Strategy,
ConnectionType: limiter.ConnectionType,
LockType: limiter.LockType,
Count: limiter.Count,
}
}
func (s *RemoteNode) convertBandwidthLimiter(limiter CS.BandwidthLimiter) *pb.BandwidthLimiter {
return &pb.BandwidthLimiter{
Id: int32(limiter.ID),
Username: limiter.Username,
Outbound: limiter.Outbound,
Strategy: limiter.Strategy,
Mode: limiter.Mode,
ConnectionType: limiter.ConnectionType,
Speed: limiter.Speed,
RawSpeed: limiter.RawSpeed,
}
}

View File

@@ -0,0 +1,139 @@
package server
import (
"context"
"errors"
"sync"
"github.com/sagernet/sing-box/adapter"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
CM "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/node_manager/manager"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
"github.com/sagernet/sing/service"
"golang.org/x/net/http2"
"google.golang.org/grpc"
)
func RegisterService(registry *boxService.Registry) {
boxService.Register[option.NodeManagerServerServiceOptions](registry, C.TypeNodeManagerServer, NewService)
}
type Service struct {
pb.UnimplementedManagerServer
boxService.Adapter
ctx context.Context
logger log.ContextLogger
listener *listener.Listener
tlsConfig tls.ServerConfig
grpcServer *grpc.Server
manager CM.Manager
options option.NodeManagerServerServiceOptions
mtx sync.Mutex
}
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerServerServiceOptions) (adapter.Service, error) {
return &Service{
Adapter: boxService.NewAdapter(C.TypeManager, tag),
ctx: ctx,
logger: logger,
listener: listener.New(listener.Options{
Context: ctx,
Logger: logger,
Network: []string{N.NetworkTCP},
Listen: options.ListenOptions,
}),
options: options,
}, nil
}
func (s *Service) AddNode(node *pb.Node, stream grpc.ServerStreamingServer[pb.NodeData]) error {
remoteNode, errChan := NewRemoteNode(s.ctx, s.logger, stream)
err := s.manager.AddNode(node.Uuid, remoteNode)
if err != nil {
if err == CM.ErrNotFound {
return err
} else {
s.logger.Error(err)
return E.New("internal error")
}
}
return <-errChan
}
func (s *Service) AcquireLock(ctx context.Context, request *pb.AcquireLockRequest) (*pb.LockData, error) {
handleId, err := s.manager.AcquireLock(int(request.LimiterId), request.Id)
if err != nil {
return nil, err
}
return &pb.LockData{HandleId: handleId}, nil
}
func (s *Service) RefreshLock(ctx context.Context, data *pb.LockData) (*pb.Empty, error) {
return nil, s.manager.RefreshLock(int(data.LimiterId), data.Id, data.HandleId)
}
func (s *Service) ReleaseLock(ctx context.Context, data *pb.LockData) (*pb.Empty, error) {
return nil, s.manager.ReleaseLock(int(data.LimiterId), data.Id, data.HandleId)
}
func (s *Service) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
service, ok := boxManager.Get(s.options.Manager)
if !ok {
return E.New("manager ", s.options.Manager, " not found")
}
s.manager, ok = service.(CM.Manager)
if !ok {
return E.New("invalid", s.options.Manager, " manager")
}
if s.options.TLS != nil {
tlsConfig, err := tls.NewServer(s.ctx, s.logger, common.PtrValueOrDefault(s.options.TLS))
if err != nil {
return err
}
s.tlsConfig = tlsConfig
}
if s.tlsConfig != nil {
err := s.tlsConfig.Start()
if err != nil {
return E.Cause(err, "create TLS config")
}
}
tcpListener, err := s.listener.ListenTCP()
if err != nil {
return err
}
if s.tlsConfig != nil {
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
}
tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)
}
s.grpcServer = grpc.NewServer()
pb.RegisterManagerServer(s.grpcServer, s)
go func() {
err = s.grpcServer.Serve(tcpListener)
if err != nil && !errors.Is(err, grpc.ErrServerStopped) {
s.logger.Error("serve error: ", err)
}
}()
return nil
}
func (s *Service) Close() error {
return nil
}