mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP
This commit is contained in:
345
service/node_manager_api/client/client.go
Normal file
345
service/node_manager_api/client/client.go
Normal file
@@ -0,0 +1,345 @@
|
||||
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_api/manager"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
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"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type APIClient struct {
|
||||
boxService.Adapter
|
||||
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
dialer N.Dialer
|
||||
creds credentials.TransportCredentials
|
||||
options option.NodeManagerAPIClientOptions
|
||||
|
||||
conn *grpc.ClientConn
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewAPIClient(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerAPIClientOptions) (*APIClient, error) {
|
||||
if options.APIKey == "" {
|
||||
return nil, E.New("missing api key")
|
||||
}
|
||||
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, logger, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds = &tlsCreds{tlsConfig}
|
||||
}
|
||||
return &APIClient{
|
||||
Adapter: boxService.NewAdapter(C.TypeManager, tag),
|
||||
ctx: metadata.AppendToOutgoingContext(ctx, "authorization", options.APIKey),
|
||||
logger: logger,
|
||||
dialer: outboundDialer,
|
||||
creds: creds,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *APIClient) 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 *APIClient) 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 *APIClient) 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 *APIClient) 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 *APIClient) AddTrafficUsage(limiterId int, n uint64) (uint64, error) {
|
||||
conn, err := s.getConn()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
client := pb.NewManagerClient(conn)
|
||||
reply, err := client.AddTrafficUsage(s.ctx, &pb.TrafficUsageRequest{LimiterId: int32(limiterId), N: n})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return reply.Remaining, nil
|
||||
}
|
||||
|
||||
func (s *APIClient) Start(stage adapter.StartStage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIClient) 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 *APIClient) 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 *APIClient) 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))
|
||||
|
||||
case pb.OpType_updateTrafficLimiter:
|
||||
s.logger.DebugContext(s.ctx, "update traffic limiter")
|
||||
node.UpdateTrafficLimiter(s.convertTrafficLimiter(data.Data.(*pb.NodeData_TrafficLimiter).TrafficLimiter))
|
||||
case pb.OpType_updateTrafficLimiters:
|
||||
s.logger.DebugContext(s.ctx, "update traffic limiters")
|
||||
limiters := data.Data.(*pb.NodeData_TrafficLimiters).TrafficLimiters.Values
|
||||
convertedLimiters := make([]CM.TrafficLimiter, len(limiters))
|
||||
for i, limiter := range limiters {
|
||||
convertedLimiters[i] = s.convertTrafficLimiter(limiter)
|
||||
}
|
||||
node.UpdateTrafficLimiters(convertedLimiters)
|
||||
case pb.OpType_deleteTrafficLimiter:
|
||||
s.logger.DebugContext(s.ctx, "delete traffic limiter")
|
||||
node.DeleteTrafficLimiter(s.convertTrafficLimiter(data.Data.(*pb.NodeData_TrafficLimiter).TrafficLimiter))
|
||||
|
||||
case pb.OpType_updateRateLimiter:
|
||||
s.logger.DebugContext(s.ctx, "update rate limiter")
|
||||
node.UpdateRateLimiter(s.convertRateLimiter(data.Data.(*pb.NodeData_RateLimiter).RateLimiter))
|
||||
case pb.OpType_updateRateLimiters:
|
||||
s.logger.DebugContext(s.ctx, "update rate limiters")
|
||||
limiters := data.Data.(*pb.NodeData_RateLimiters).RateLimiters.Values
|
||||
convertedLimiters := make([]CM.RateLimiter, len(limiters))
|
||||
for i, limiter := range limiters {
|
||||
convertedLimiters[i] = s.convertRateLimiter(limiter)
|
||||
}
|
||||
node.UpdateRateLimiters(convertedLimiters)
|
||||
case pb.OpType_deleteRateLimiter:
|
||||
s.logger.DebugContext(s.ctx, "delete rate limiter")
|
||||
node.DeleteRateLimiter(s.convertRateLimiter(data.Data.(*pb.NodeData_RateLimiter).RateLimiter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIClient) convertUser(user *pb.User) CM.User {
|
||||
return CM.User{
|
||||
ID: int(user.Id),
|
||||
Username: user.Username,
|
||||
Inbound: user.Inbound,
|
||||
Type: user.Type,
|
||||
UUID: user.Uuid,
|
||||
Password: user.Password,
|
||||
Secret: user.Secret,
|
||||
Flow: user.Flow,
|
||||
AlterID: int(user.AlterId),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIClient) convertBandwidthLimiter(limiter *pb.BandwidthLimiter) CM.BandwidthLimiter {
|
||||
return CM.BandwidthLimiter{
|
||||
ID: int(limiter.Id),
|
||||
Username: limiter.Username,
|
||||
Outbound: limiter.Outbound,
|
||||
Strategy: limiter.Strategy,
|
||||
ConnectionType: limiter.ConnectionType,
|
||||
Mode: limiter.Mode,
|
||||
FlowKeys: limiter.FlowKeys,
|
||||
Speed: limiter.Speed,
|
||||
RawSpeed: limiter.RawSpeed,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIClient) 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,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIClient) convertTrafficLimiter(limiter *pb.TrafficLimiter) CM.TrafficLimiter {
|
||||
return CM.TrafficLimiter{
|
||||
ID: int(limiter.Id),
|
||||
Username: limiter.Username,
|
||||
Outbound: limiter.Outbound,
|
||||
Strategy: limiter.Strategy,
|
||||
Mode: limiter.Mode,
|
||||
RawUsed: limiter.RawUsed,
|
||||
Quota: limiter.Quota,
|
||||
RawQuota: limiter.RawQuota,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIClient) convertRateLimiter(limiter *pb.RateLimiter) CM.RateLimiter {
|
||||
return CM.RateLimiter{
|
||||
ID: int(limiter.Id),
|
||||
Username: limiter.Username,
|
||||
Outbound: limiter.Outbound,
|
||||
Strategy: limiter.Strategy,
|
||||
ConnectionType: limiter.ConnectionType,
|
||||
Count: limiter.Count,
|
||||
Interval: limiter.Interval,
|
||||
}
|
||||
}
|
||||
44
service/node_manager_api/client/tls.go
Normal file
44
service/node_manager_api/client/tls.go
Normal 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
|
||||
}
|
||||
1556
service/node_manager_api/manager/manager.pb.go
Normal file
1556
service/node_manager_api/manager/manager.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
155
service/node_manager_api/manager/manager.proto
Normal file
155
service/node_manager_api/manager/manager.proto
Normal file
@@ -0,0 +1,155 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "github.com/sagernet/sing-box/service/node_manager_api/manager";
|
||||
|
||||
package node_manager_api.v1;
|
||||
|
||||
service Manager {
|
||||
rpc AddNode (Node) returns (stream NodeData);
|
||||
rpc AcquireLock(AcquireLockRequest) returns (LockData);
|
||||
rpc RefreshLock(LockData) returns (Empty);
|
||||
rpc ReleaseLock(LockData) returns (Empty);
|
||||
rpc AddTrafficUsage(TrafficUsageRequest) returns (TrafficUsageReply);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
updateTrafficLimiters = 9;
|
||||
updateTrafficLimiter = 10;
|
||||
deleteTrafficLimiter = 11;
|
||||
|
||||
updateRateLimiters = 12;
|
||||
updateRateLimiter = 13;
|
||||
deleteRateLimiter = 14;
|
||||
}
|
||||
|
||||
message User {
|
||||
int32 id = 1;
|
||||
string username = 2;
|
||||
string inbound = 3;
|
||||
string type = 4;
|
||||
string uuid = 5;
|
||||
string password = 6;
|
||||
string secret = 7;
|
||||
string flow = 8;
|
||||
int32 alter_id = 9;
|
||||
}
|
||||
|
||||
message UserList {
|
||||
repeated User values = 1;
|
||||
}
|
||||
|
||||
message BandwidthLimiter {
|
||||
int32 id = 1;
|
||||
string username = 2;
|
||||
string outbound = 3;
|
||||
string strategy = 4;
|
||||
string connection_type = 5;
|
||||
string mode = 6;
|
||||
repeated string flow_keys = 7;
|
||||
string speed = 8;
|
||||
uint64 raw_speed = 9;
|
||||
}
|
||||
|
||||
message BandwidthLimiterList {
|
||||
repeated BandwidthLimiter values = 1;
|
||||
}
|
||||
|
||||
message ConnectionLimiter {
|
||||
int32 id = 1;
|
||||
string username = 2;
|
||||
string outbound = 3;
|
||||
string strategy = 4;
|
||||
string connection_type = 5;
|
||||
string lock_type = 6;
|
||||
uint32 count = 7;
|
||||
}
|
||||
|
||||
message ConnectionLimiterList {
|
||||
repeated ConnectionLimiter values = 1;
|
||||
}
|
||||
|
||||
message TrafficLimiter {
|
||||
int32 id = 1;
|
||||
string username = 2;
|
||||
string outbound = 3;
|
||||
string strategy = 4;
|
||||
string mode = 5;
|
||||
uint64 raw_used = 6;
|
||||
string quota = 7;
|
||||
uint64 raw_quota = 8;
|
||||
}
|
||||
|
||||
message TrafficLimiterList {
|
||||
repeated TrafficLimiter values = 1;
|
||||
}
|
||||
|
||||
message RateLimiter {
|
||||
int32 id = 1;
|
||||
string username = 2;
|
||||
string outbound = 3;
|
||||
string strategy = 4;
|
||||
string connection_type = 5;
|
||||
uint32 count = 6;
|
||||
string interval = 7;
|
||||
}
|
||||
|
||||
message RateLimiterList {
|
||||
repeated RateLimiter 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;
|
||||
TrafficLimiterList traffic_limiters = 8;
|
||||
TrafficLimiter traffic_limiter = 9;
|
||||
RateLimiterList rate_limiters = 10;
|
||||
RateLimiter rate_limiter = 11;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
message AcquireLockRequest {
|
||||
int32 limiter_id = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message LockData {
|
||||
int32 limiter_id = 1;
|
||||
string id = 2;
|
||||
string handleId = 3;
|
||||
}
|
||||
|
||||
message TrafficUsageRequest {
|
||||
int32 limiter_id = 1;
|
||||
uint64 n = 2;
|
||||
}
|
||||
|
||||
message TrafficUsageReply {
|
||||
uint64 remaining = 1;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
277
service/node_manager_api/manager/manager_grpc.pb.go
Normal file
277
service/node_manager_api/manager/manager_grpc.pb.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc v6.31.1
|
||||
// source: service/node_manager_api/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 = "/node_manager_api.v1.Manager/AddNode"
|
||||
Manager_AcquireLock_FullMethodName = "/node_manager_api.v1.Manager/AcquireLock"
|
||||
Manager_RefreshLock_FullMethodName = "/node_manager_api.v1.Manager/RefreshLock"
|
||||
Manager_ReleaseLock_FullMethodName = "/node_manager_api.v1.Manager/ReleaseLock"
|
||||
Manager_AddTrafficUsage_FullMethodName = "/node_manager_api.v1.Manager/AddTrafficUsage"
|
||||
)
|
||||
|
||||
// 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)
|
||||
AddTrafficUsage(ctx context.Context, in *TrafficUsageRequest, opts ...grpc.CallOption) (*TrafficUsageReply, 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
|
||||
}
|
||||
|
||||
func (c *managerClient) AddTrafficUsage(ctx context.Context, in *TrafficUsageRequest, opts ...grpc.CallOption) (*TrafficUsageReply, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(TrafficUsageReply)
|
||||
err := c.cc.Invoke(ctx, Manager_AddTrafficUsage_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)
|
||||
AddTrafficUsage(context.Context, *TrafficUsageRequest) (*TrafficUsageReply, 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) AddTrafficUsage(context.Context, *TrafficUsageRequest) (*TrafficUsageReply, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method AddTrafficUsage 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)
|
||||
}
|
||||
|
||||
func _Manager_AddTrafficUsage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TrafficUsageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagerServer).AddTrafficUsage(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Manager_AddTrafficUsage_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagerServer).AddTrafficUsage(ctx, req.(*TrafficUsageRequest))
|
||||
}
|
||||
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: "node_manager_api.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,
|
||||
},
|
||||
{
|
||||
MethodName: "AddTrafficUsage",
|
||||
Handler: _Manager_AddTrafficUsage_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "AddNode",
|
||||
Handler: _Manager_AddNode_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "service/node_manager_api/manager/manager.proto",
|
||||
}
|
||||
303
service/node_manager_api/server/node.go
Normal file
303
service/node_manager_api/server/node.go
Normal file
@@ -0,0 +1,303 @@
|
||||
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_api/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]
|
||||
|
||||
err error
|
||||
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) UpdateTrafficLimiter(limiter CS.TrafficLimiter) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.send(&pb.NodeData{
|
||||
Op: pb.OpType_updateTrafficLimiter,
|
||||
Data: &pb.NodeData_TrafficLimiter{TrafficLimiter: s.convertTrafficLimiter(limiter)},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RemoteNode) UpdateTrafficLimiters(limiters []CS.TrafficLimiter) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
pbLimiters := make([]*pb.TrafficLimiter, len(limiters))
|
||||
for i, limiters := range limiters {
|
||||
pbLimiters[i] = s.convertTrafficLimiter(limiters)
|
||||
}
|
||||
s.send(&pb.NodeData{
|
||||
Op: pb.OpType_updateTrafficLimiters,
|
||||
Data: &pb.NodeData_TrafficLimiters{TrafficLimiters: &pb.TrafficLimiterList{Values: pbLimiters}},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RemoteNode) DeleteTrafficLimiter(limiter CS.TrafficLimiter) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.send(&pb.NodeData{
|
||||
Op: pb.OpType_deleteTrafficLimiter,
|
||||
Data: &pb.NodeData_TrafficLimiter{TrafficLimiter: s.convertTrafficLimiter(limiter)},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RemoteNode) UpdateRateLimiter(limiter CS.RateLimiter) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.send(&pb.NodeData{
|
||||
Op: pb.OpType_updateRateLimiter,
|
||||
Data: &pb.NodeData_RateLimiter{RateLimiter: s.convertRateLimiter(limiter)},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RemoteNode) UpdateRateLimiters(limiters []CS.RateLimiter) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
pbLimiters := make([]*pb.RateLimiter, len(limiters))
|
||||
for i, limiters := range limiters {
|
||||
pbLimiters[i] = s.convertRateLimiter(limiters)
|
||||
}
|
||||
s.send(&pb.NodeData{
|
||||
Op: pb.OpType_updateRateLimiters,
|
||||
Data: &pb.NodeData_RateLimiters{RateLimiters: &pb.RateLimiterList{Values: pbLimiters}},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RemoteNode) DeleteRateLimiter(limiter CS.RateLimiter) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.send(&pb.NodeData{
|
||||
Op: pb.OpType_deleteRateLimiter,
|
||||
Data: &pb.NodeData_RateLimiter{RateLimiter: s.convertRateLimiter(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.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.close(E.New("server connection is closed"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RemoteNode) send(data *pb.NodeData) {
|
||||
if s.err != nil {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.err = err
|
||||
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,
|
||||
Inbound: user.Inbound,
|
||||
Type: user.Type,
|
||||
Uuid: user.UUID,
|
||||
Password: user.Password,
|
||||
Secret: user.Secret,
|
||||
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,
|
||||
ConnectionType: limiter.ConnectionType,
|
||||
Mode: limiter.Mode,
|
||||
FlowKeys: limiter.FlowKeys,
|
||||
Speed: limiter.Speed,
|
||||
RawSpeed: limiter.RawSpeed,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteNode) convertTrafficLimiter(limiter CS.TrafficLimiter) *pb.TrafficLimiter {
|
||||
return &pb.TrafficLimiter{
|
||||
Id: int32(limiter.ID),
|
||||
Username: limiter.Username,
|
||||
Outbound: limiter.Outbound,
|
||||
Strategy: limiter.Strategy,
|
||||
Mode: limiter.Mode,
|
||||
RawUsed: limiter.RawUsed,
|
||||
Quota: limiter.Quota,
|
||||
RawQuota: limiter.RawQuota,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteNode) convertRateLimiter(limiter CS.RateLimiter) *pb.RateLimiter {
|
||||
return &pb.RateLimiter{
|
||||
Id: int32(limiter.ID),
|
||||
Username: limiter.Username,
|
||||
Outbound: limiter.Outbound,
|
||||
Strategy: limiter.Strategy,
|
||||
ConnectionType: limiter.ConnectionType,
|
||||
Count: limiter.Count,
|
||||
Interval: limiter.Interval,
|
||||
}
|
||||
}
|
||||
182
service/node_manager_api/server/server.go
Normal file
182
service/node_manager_api/server/server.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"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_api/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"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type APIServer struct {
|
||||
pb.UnimplementedManagerServer
|
||||
boxService.Adapter
|
||||
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
grpcServer *grpc.Server
|
||||
manager CM.NodeManager
|
||||
options option.NodeManagerAPIServerOptions
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewAPIServer(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerAPIServerOptions) (*APIServer, error) {
|
||||
if options.APIKey == "" {
|
||||
return nil, E.New("missing api key")
|
||||
}
|
||||
return &APIServer{
|
||||
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 *APIServer) 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 *APIServer) 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 *APIServer) RefreshLock(ctx context.Context, data *pb.LockData) (*pb.Empty, error) {
|
||||
return nil, s.manager.RefreshLock(int(data.LimiterId), data.Id, data.HandleId)
|
||||
}
|
||||
|
||||
func (s *APIServer) ReleaseLock(ctx context.Context, data *pb.LockData) (*pb.Empty, error) {
|
||||
return nil, s.manager.ReleaseLock(int(data.LimiterId), data.Id, data.HandleId)
|
||||
}
|
||||
|
||||
func (s *APIServer) AddTrafficUsage(ctx context.Context, request *pb.TrafficUsageRequest) (*pb.TrafficUsageReply, error) {
|
||||
remaining, err := s.manager.AddTrafficUsage(int(request.LimiterId), request.N)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.TrafficUsageReply{Remaining: remaining}, nil
|
||||
}
|
||||
|
||||
func (s *APIServer) 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.NodeManager)
|
||||
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(
|
||||
grpc.ChainUnaryInterceptor(s.unaryAuthInterceptor),
|
||||
grpc.StreamInterceptor(s.streamAuthInterceptor),
|
||||
)
|
||||
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 *APIServer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIServer) unaryAuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
if err := s.authorize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
func (s *APIServer) streamAuthInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
if err := s.authorize(ss.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
return handler(srv, ss)
|
||||
}
|
||||
|
||||
func (s *APIServer) authorize(ctx context.Context) error {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return status.Error(codes.Unauthenticated, "missing api key")
|
||||
}
|
||||
values := md.Get("authorization")
|
||||
if len(values) == 0 {
|
||||
return status.Error(codes.Unauthenticated, "missing api key")
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(values[0]), []byte(s.options.APIKey)) == 0 {
|
||||
return status.Error(codes.Unauthenticated, "invalid api key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
31
service/node_manager_api/service.go
Normal file
31
service/node_manager_api/service.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package node_manager_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/service/node_manager_api/client"
|
||||
"github.com/sagernet/sing-box/service/node_manager_api/server"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func RegisterService(registry *boxService.Registry) {
|
||||
boxService.Register[option.NodeManagerAPIOptions](registry, C.TypeNodeManagerAPI, NewService)
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerAPIOptions) (adapter.Service, error) {
|
||||
switch options.APIType {
|
||||
case C.NodeManagerAPIServer:
|
||||
return server.NewAPIServer(ctx, logger, tag, options.ServerOptions)
|
||||
case C.NodeManagerAPIClient:
|
||||
return client.NewAPIClient(ctx, logger, tag, options.ClientOptions)
|
||||
case "":
|
||||
return nil, E.New("missing api type")
|
||||
default:
|
||||
return nil, E.New("unknown api type: ", options.APIType)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user