Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP

This commit is contained in:
Sergei Maklagin
2026-05-11 00:59:35 +03:00
parent 652e0baf57
commit 3bd162ed6f
241 changed files with 36409 additions and 4086 deletions

View File

@@ -0,0 +1,109 @@
package client
import (
"context"
"net"
"sync"
"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"
pb "github.com/sagernet/sing-box/service/manager_api/grpc/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"
"google.golang.org/grpc/metadata"
)
type Client struct {
boxService.Adapter
ctx context.Context
logger log.ContextLogger
dialer N.Dialer
creds credentials.TransportCredentials
options option.ManagerAPIClientOptions
conn *grpc.ClientConn
mtx sync.Mutex
}
func NewClient(ctx context.Context, logger log.ContextLogger, tag string, options option.ManagerAPIClientOptions) (*Client, 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, logger, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
creds = &tlsCreds{config: tlsConfig}
}
return &Client{
Adapter: boxService.NewAdapter(C.TypeManagerAPI, tag),
ctx: ctx,
logger: logger,
dialer: outboundDialer,
creds: creds,
options: options,
}, nil
}
func (s *Client) Start(stage adapter.StartStage) error { return nil }
func (s *Client) Close() error {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.conn != nil {
return s.conn.Close()
}
return nil
}
func (s *Client) client() (pb.ManagerClient, error) {
conn, err := s.getConn()
if err != nil {
return nil, err
}
return pb.NewManagerClient(conn), nil
}
func (s *Client) 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
}
}
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
}
s.conn = conn
return conn, nil
}
func (s *Client) callContext() context.Context {
if s.options.APIKey == "" {
return s.ctx
}
return metadata.AppendToOutgoingContext(s.ctx, "authorization", s.options.APIKey)
}

View File

@@ -0,0 +1,146 @@
package client
import (
"time"
CM "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/manager_api/grpc/manager"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func mapError(err error) error {
if err == nil {
return nil
}
if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound {
return CM.ErrNotFound
}
return err
}
func toIntSlice(values []int32) []int {
out := make([]int, len(values))
for i, v := range values {
out[i] = int(v)
}
return out
}
func toInt32Slice(values []int) []int32 {
out := make([]int32, len(values))
for i, v := range values {
out[i] = int32(v)
}
return out
}
func timeFromNano(ns int64) time.Time { return time.Unix(0, ns) }
func convertSquad(v *pb.Squad) CM.Squad {
return CM.Squad{
ID: int(v.GetId()),
Name: v.GetName(),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertNode(v *pb.Node) CM.Node {
return CM.Node{
UUID: v.GetUuid(),
Name: v.GetName(),
SquadIDs: toIntSlice(v.GetSquadIds()),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertUser(v *pb.User) CM.User {
return CM.User{
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Inbound: v.GetInbound(),
Type: v.GetType(),
UUID: v.GetUuid(),
Password: v.GetPassword(),
Secret: v.GetSecret(),
Flow: v.GetFlow(),
AlterID: int(v.GetAlterId()),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertBandwidthLimiter(v *pb.BandwidthLimiter) CM.BandwidthLimiter {
return CM.BandwidthLimiter{
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Outbound: v.GetOutbound(),
Strategy: v.GetStrategy(),
ConnectionType: v.GetConnectionType(),
Mode: v.GetMode(),
FlowKeys: v.GetFlowKeys(),
Speed: v.GetSpeed(),
RawSpeed: v.GetRawSpeed(),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertTrafficLimiter(v *pb.TrafficLimiter) CM.TrafficLimiter {
return CM.TrafficLimiter{
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Outbound: v.GetOutbound(),
Strategy: v.GetStrategy(),
Mode: v.GetMode(),
RawUsed: v.GetRawUsed(),
Quota: v.GetQuota(),
RawQuota: v.GetRawQuota(),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertConnectionLimiter(v *pb.ConnectionLimiter) CM.ConnectionLimiter {
return CM.ConnectionLimiter{
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Outbound: v.GetOutbound(),
Strategy: v.GetStrategy(),
ConnectionType: v.GetConnectionType(),
LockType: v.GetLockType(),
Count: v.GetCount(),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertRateLimiter(v *pb.RateLimiter) CM.RateLimiter {
return CM.RateLimiter{
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Outbound: v.GetOutbound(),
Strategy: v.GetStrategy(),
ConnectionType: v.GetConnectionType(),
Count: v.GetCount(),
Interval: v.GetInterval(),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
func convertFilters(filters map[string][]string) *pb.Filters {
values := make(map[string]*pb.StringList, len(filters))
for k, v := range filters {
values[k] = &pb.StringList{Values: append([]string(nil), v...)}
}
return &pb.Filters{Values: values}
}

View File

@@ -0,0 +1,658 @@
package client
import (
CM "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/manager_api/grpc/manager"
E "github.com/sagernet/sing/common/exceptions"
)
var _ CM.Manager = (*Client)(nil)
func (s *Client) CreateSquad(in CM.SquadCreate) (CM.Squad, error) {
c, err := s.client()
if err != nil {
return CM.Squad{}, err
}
reply, err := c.CreateSquad(s.callContext(), &pb.SquadCreate{Name: in.Name})
if err != nil {
return CM.Squad{}, mapError(err)
}
return convertSquad(reply), nil
}
func (s *Client) GetSquads(filters map[string][]string) ([]CM.Squad, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetSquads(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.Squad, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertSquad(v)
}
return out, nil
}
func (s *Client) GetSquadsCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetSquadsCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetSquad(id int) (CM.Squad, error) {
c, err := s.client()
if err != nil {
return CM.Squad{}, err
}
reply, err := c.GetSquad(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.Squad{}, mapError(err)
}
return convertSquad(reply), nil
}
func (s *Client) UpdateSquad(id int, in CM.SquadUpdate) (CM.Squad, error) {
c, err := s.client()
if err != nil {
return CM.Squad{}, err
}
reply, err := c.UpdateSquad(s.callContext(), &pb.SquadUpdateRequest{
Id: int32(id),
Update: &pb.SquadUpdate{Name: in.Name},
})
if err != nil {
return CM.Squad{}, mapError(err)
}
return convertSquad(reply), nil
}
func (s *Client) DeleteSquad(id int) (CM.Squad, error) {
c, err := s.client()
if err != nil {
return CM.Squad{}, err
}
reply, err := c.DeleteSquad(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.Squad{}, mapError(err)
}
return convertSquad(reply), nil
}
func (s *Client) CreateNode(in CM.NodeCreate) (CM.Node, error) {
c, err := s.client()
if err != nil {
return CM.Node{}, err
}
reply, err := c.CreateNode(s.callContext(), &pb.NodeCreate{
Uuid: in.UUID,
Name: in.Name,
SquadIds: toInt32Slice(in.SquadIDs),
})
if err != nil {
return CM.Node{}, mapError(err)
}
return convertNode(reply), nil
}
func (s *Client) GetNodes(filters map[string][]string) ([]CM.Node, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetNodes(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.Node, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertNode(v)
}
return out, nil
}
func (s *Client) GetNodesCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetNodesCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetNode(uuid string) (CM.Node, error) {
c, err := s.client()
if err != nil {
return CM.Node{}, err
}
reply, err := c.GetNode(s.callContext(), &pb.UuidRequest{Uuid: uuid})
if err != nil {
return CM.Node{}, mapError(err)
}
return convertNode(reply), nil
}
func (s *Client) GetNodeStatus(uuid string) (string, error) {
c, err := s.client()
if err != nil {
return "", err
}
reply, err := c.GetNodeStatus(s.callContext(), &pb.UuidRequest{Uuid: uuid})
if err != nil {
return "", mapError(err)
}
return reply.GetStatus(), nil
}
func (s *Client) UpdateNode(uuid string, in CM.NodeUpdate) (CM.Node, error) {
c, err := s.client()
if err != nil {
return CM.Node{}, err
}
reply, err := c.UpdateNode(s.callContext(), &pb.NodeUpdateRequest{
Uuid: uuid,
Update: &pb.NodeUpdate{Name: in.Name},
})
if err != nil {
return CM.Node{}, mapError(err)
}
return convertNode(reply), nil
}
func (s *Client) DeleteNode(uuid string) (CM.Node, error) {
c, err := s.client()
if err != nil {
return CM.Node{}, err
}
reply, err := c.DeleteNode(s.callContext(), &pb.UuidRequest{Uuid: uuid})
if err != nil {
return CM.Node{}, mapError(err)
}
return convertNode(reply), nil
}
func (s *Client) CreateUser(in CM.UserCreate) (CM.User, error) {
c, err := s.client()
if err != nil {
return CM.User{}, err
}
reply, err := c.CreateUser(s.callContext(), &pb.UserCreate{
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Inbound: in.Inbound,
Type: in.Type,
Uuid: in.UUID,
Password: in.Password,
Secret: in.Secret,
Flow: in.Flow,
AlterId: int32(in.AlterID),
})
if err != nil {
return CM.User{}, mapError(err)
}
return convertUser(reply), nil
}
func (s *Client) GetUsers(filters map[string][]string) ([]CM.User, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetUsers(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.User, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertUser(v)
}
return out, nil
}
func (s *Client) GetUsersCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetUsersCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetUser(id int) (CM.User, error) {
c, err := s.client()
if err != nil {
return CM.User{}, err
}
reply, err := c.GetUser(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.User{}, mapError(err)
}
return convertUser(reply), nil
}
func (s *Client) UpdateUser(id int, in CM.UserUpdate) (CM.User, error) {
c, err := s.client()
if err != nil {
return CM.User{}, err
}
reply, err := c.UpdateUser(s.callContext(), &pb.UserUpdateRequest{
Id: int32(id),
Update: &pb.UserUpdate{
Uuid: in.UUID,
Password: in.Password,
Secret: in.Secret,
Flow: in.Flow,
AlterId: int32(in.AlterID),
},
})
if err != nil {
return CM.User{}, mapError(err)
}
return convertUser(reply), nil
}
func (s *Client) DeleteUser(id int) (CM.User, error) {
c, err := s.client()
if err != nil {
return CM.User{}, err
}
reply, err := c.DeleteUser(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.User{}, mapError(err)
}
return convertUser(reply), nil
}
func (s *Client) CreateBandwidthLimiter(in CM.BandwidthLimiterCreate) (CM.BandwidthLimiter, error) {
c, err := s.client()
if err != nil {
return CM.BandwidthLimiter{}, err
}
reply, err := c.CreateBandwidthLimiter(s.callContext(), &pb.BandwidthLimiterCreate{
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
Mode: in.Mode,
FlowKeys: in.FlowKeys,
Speed: in.Speed,
})
if err != nil {
return CM.BandwidthLimiter{}, mapError(err)
}
return convertBandwidthLimiter(reply), nil
}
func (s *Client) GetBandwidthLimiters(filters map[string][]string) ([]CM.BandwidthLimiter, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetBandwidthLimiters(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.BandwidthLimiter, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertBandwidthLimiter(v)
}
return out, nil
}
func (s *Client) GetBandwidthLimitersCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetBandwidthLimitersCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetBandwidthLimiter(id int) (CM.BandwidthLimiter, error) {
c, err := s.client()
if err != nil {
return CM.BandwidthLimiter{}, err
}
reply, err := c.GetBandwidthLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.BandwidthLimiter{}, mapError(err)
}
return convertBandwidthLimiter(reply), nil
}
func (s *Client) UpdateBandwidthLimiter(id int, in CM.BandwidthLimiterUpdate) (CM.BandwidthLimiter, error) {
c, err := s.client()
if err != nil {
return CM.BandwidthLimiter{}, err
}
reply, err := c.UpdateBandwidthLimiter(s.callContext(), &pb.BandwidthLimiterUpdateRequest{
Id: int32(id),
Update: &pb.BandwidthLimiterUpdate{
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
Mode: in.Mode,
FlowKeys: in.FlowKeys,
Speed: in.Speed,
},
})
if err != nil {
return CM.BandwidthLimiter{}, mapError(err)
}
return convertBandwidthLimiter(reply), nil
}
func (s *Client) DeleteBandwidthLimiter(id int) (CM.BandwidthLimiter, error) {
c, err := s.client()
if err != nil {
return CM.BandwidthLimiter{}, err
}
reply, err := c.DeleteBandwidthLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.BandwidthLimiter{}, mapError(err)
}
return convertBandwidthLimiter(reply), nil
}
func (s *Client) CreateTrafficLimiter(in CM.TrafficLimiterCreate) (CM.TrafficLimiter, error) {
c, err := s.client()
if err != nil {
return CM.TrafficLimiter{}, err
}
reply, err := c.CreateTrafficLimiter(s.callContext(), &pb.TrafficLimiterCreate{
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
Mode: in.Mode,
Quota: in.Quota,
})
if err != nil {
return CM.TrafficLimiter{}, mapError(err)
}
return convertTrafficLimiter(reply), nil
}
func (s *Client) GetTrafficLimiters(filters map[string][]string) ([]CM.TrafficLimiter, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetTrafficLimiters(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.TrafficLimiter, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertTrafficLimiter(v)
}
return out, nil
}
func (s *Client) GetTrafficLimitersCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetTrafficLimitersCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetTrafficLimiter(id int) (CM.TrafficLimiter, error) {
c, err := s.client()
if err != nil {
return CM.TrafficLimiter{}, err
}
reply, err := c.GetTrafficLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.TrafficLimiter{}, mapError(err)
}
return convertTrafficLimiter(reply), nil
}
func (s *Client) UpdateTrafficLimiter(id int, in CM.TrafficLimiterUpdate) (CM.TrafficLimiter, error) {
c, err := s.client()
if err != nil {
return CM.TrafficLimiter{}, err
}
reply, err := c.UpdateTrafficLimiter(s.callContext(), &pb.TrafficLimiterUpdateRequest{
Id: int32(id),
Update: &pb.TrafficLimiterUpdate{
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
Mode: in.Mode,
Quota: in.Quota,
},
})
if err != nil {
return CM.TrafficLimiter{}, mapError(err)
}
return convertTrafficLimiter(reply), nil
}
func (s *Client) UpdateTrafficLimiterUsed(_ int, _ uint64) (CM.TrafficLimiter, error) {
return CM.TrafficLimiter{}, E.New("UpdateTrafficLimiterUsed not implemented over gRPC")
}
func (s *Client) DeleteTrafficLimiter(id int) (CM.TrafficLimiter, error) {
c, err := s.client()
if err != nil {
return CM.TrafficLimiter{}, err
}
reply, err := c.DeleteTrafficLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.TrafficLimiter{}, mapError(err)
}
return convertTrafficLimiter(reply), nil
}
func (s *Client) CreateConnectionLimiter(in CM.ConnectionLimiterCreate) (CM.ConnectionLimiter, error) {
c, err := s.client()
if err != nil {
return CM.ConnectionLimiter{}, err
}
reply, err := c.CreateConnectionLimiter(s.callContext(), &pb.ConnectionLimiterCreate{
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
LockType: in.LockType,
Count: in.Count,
})
if err != nil {
return CM.ConnectionLimiter{}, mapError(err)
}
return convertConnectionLimiter(reply), nil
}
func (s *Client) GetConnectionLimiters(filters map[string][]string) ([]CM.ConnectionLimiter, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetConnectionLimiters(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.ConnectionLimiter, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertConnectionLimiter(v)
}
return out, nil
}
func (s *Client) GetConnectionLimitersCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetConnectionLimitersCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetConnectionLimiter(id int) (CM.ConnectionLimiter, error) {
c, err := s.client()
if err != nil {
return CM.ConnectionLimiter{}, err
}
reply, err := c.GetConnectionLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.ConnectionLimiter{}, mapError(err)
}
return convertConnectionLimiter(reply), nil
}
func (s *Client) UpdateConnectionLimiter(id int, in CM.ConnectionLimiterUpdate) (CM.ConnectionLimiter, error) {
c, err := s.client()
if err != nil {
return CM.ConnectionLimiter{}, err
}
reply, err := c.UpdateConnectionLimiter(s.callContext(), &pb.ConnectionLimiterUpdateRequest{
Id: int32(id),
Update: &pb.ConnectionLimiterUpdate{
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
LockType: in.LockType,
Count: in.Count,
},
})
if err != nil {
return CM.ConnectionLimiter{}, mapError(err)
}
return convertConnectionLimiter(reply), nil
}
func (s *Client) DeleteConnectionLimiter(id int) (CM.ConnectionLimiter, error) {
c, err := s.client()
if err != nil {
return CM.ConnectionLimiter{}, err
}
reply, err := c.DeleteConnectionLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.ConnectionLimiter{}, mapError(err)
}
return convertConnectionLimiter(reply), nil
}
func (s *Client) CreateRateLimiter(in CM.RateLimiterCreate) (CM.RateLimiter, error) {
c, err := s.client()
if err != nil {
return CM.RateLimiter{}, err
}
reply, err := c.CreateRateLimiter(s.callContext(), &pb.RateLimiterCreate{
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
Count: in.Count,
Interval: in.Interval,
})
if err != nil {
return CM.RateLimiter{}, mapError(err)
}
return convertRateLimiter(reply), nil
}
func (s *Client) GetRateLimiters(filters map[string][]string) ([]CM.RateLimiter, error) {
c, err := s.client()
if err != nil {
return nil, err
}
reply, err := c.GetRateLimiters(s.callContext(), convertFilters(filters))
if err != nil {
return nil, mapError(err)
}
out := make([]CM.RateLimiter, len(reply.GetValues()))
for i, v := range reply.GetValues() {
out[i] = convertRateLimiter(v)
}
return out, nil
}
func (s *Client) GetRateLimitersCount(filters map[string][]string) (int, error) {
c, err := s.client()
if err != nil {
return 0, err
}
reply, err := c.GetRateLimitersCount(s.callContext(), convertFilters(filters))
if err != nil {
return 0, mapError(err)
}
return int(reply.GetCount()), nil
}
func (s *Client) GetRateLimiter(id int) (CM.RateLimiter, error) {
c, err := s.client()
if err != nil {
return CM.RateLimiter{}, err
}
reply, err := c.GetRateLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.RateLimiter{}, mapError(err)
}
return convertRateLimiter(reply), nil
}
func (s *Client) UpdateRateLimiter(id int, in CM.RateLimiterUpdate) (CM.RateLimiter, error) {
c, err := s.client()
if err != nil {
return CM.RateLimiter{}, err
}
reply, err := c.UpdateRateLimiter(s.callContext(), &pb.RateLimiterUpdateRequest{
Id: int32(id),
Update: &pb.RateLimiterUpdate{
Username: in.Username,
Outbound: in.Outbound,
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
Count: in.Count,
Interval: in.Interval,
},
})
if err != nil {
return CM.RateLimiter{}, mapError(err)
}
return convertRateLimiter(reply), nil
}
func (s *Client) DeleteRateLimiter(id int) (CM.RateLimiter, error) {
c, err := s.client()
if err != nil {
return CM.RateLimiter{}, err
}
reply, err := c.DeleteRateLimiter(s.callContext(), &pb.IdRequest{Id: int32(id)})
if err != nil {
return CM.RateLimiter{}, mapError(err)
}
return convertRateLimiter(reply), nil
}

View File

@@ -0,0 +1,43 @@
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 {
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,345 @@
syntax = "proto3";
option go_package = "github.com/sagernet/sing-box/service/manager_api/grpc/manager";
package manager_api.v1;
service Manager {
rpc CreateSquad (SquadCreate) returns (Squad);
rpc GetSquads (Filters) returns (SquadList);
rpc GetSquadsCount (Filters) returns (CountReply);
rpc GetSquad (IdRequest) returns (Squad);
rpc UpdateSquad (SquadUpdateRequest) returns (Squad);
rpc DeleteSquad (IdRequest) returns (Squad);
rpc CreateNode (NodeCreate) returns (Node);
rpc GetNodes (Filters) returns (NodeList);
rpc GetNodesCount (Filters) returns (CountReply);
rpc GetNode (UuidRequest) returns (Node);
rpc GetNodeStatus (UuidRequest) returns (NodeStatusReply);
rpc UpdateNode (NodeUpdateRequest) returns (Node);
rpc DeleteNode (UuidRequest) returns (Node);
rpc CreateUser (UserCreate) returns (User);
rpc GetUsers (Filters) returns (UserList);
rpc GetUsersCount (Filters) returns (CountReply);
rpc GetUser (IdRequest) returns (User);
rpc UpdateUser (UserUpdateRequest) returns (User);
rpc DeleteUser (IdRequest) returns (User);
rpc CreateBandwidthLimiter (BandwidthLimiterCreate) returns (BandwidthLimiter);
rpc GetBandwidthLimiters (Filters) returns (BandwidthLimiterList);
rpc GetBandwidthLimitersCount (Filters) returns (CountReply);
rpc GetBandwidthLimiter (IdRequest) returns (BandwidthLimiter);
rpc UpdateBandwidthLimiter (BandwidthLimiterUpdateRequest) returns (BandwidthLimiter);
rpc DeleteBandwidthLimiter (IdRequest) returns (BandwidthLimiter);
rpc CreateTrafficLimiter (TrafficLimiterCreate) returns (TrafficLimiter);
rpc GetTrafficLimiters (Filters) returns (TrafficLimiterList);
rpc GetTrafficLimitersCount (Filters) returns (CountReply);
rpc GetTrafficLimiter (IdRequest) returns (TrafficLimiter);
rpc UpdateTrafficLimiter (TrafficLimiterUpdateRequest) returns (TrafficLimiter);
rpc DeleteTrafficLimiter (IdRequest) returns (TrafficLimiter);
rpc CreateConnectionLimiter (ConnectionLimiterCreate) returns (ConnectionLimiter);
rpc GetConnectionLimiters (Filters) returns (ConnectionLimiterList);
rpc GetConnectionLimitersCount (Filters) returns (CountReply);
rpc GetConnectionLimiter (IdRequest) returns (ConnectionLimiter);
rpc UpdateConnectionLimiter (ConnectionLimiterUpdateRequest) returns (ConnectionLimiter);
rpc DeleteConnectionLimiter (IdRequest) returns (ConnectionLimiter);
rpc CreateRateLimiter (RateLimiterCreate) returns (RateLimiter);
rpc GetRateLimiters (Filters) returns (RateLimiterList);
rpc GetRateLimitersCount (Filters) returns (CountReply);
rpc GetRateLimiter (IdRequest) returns (RateLimiter);
rpc UpdateRateLimiter (RateLimiterUpdateRequest) returns (RateLimiter);
rpc DeleteRateLimiter (IdRequest) returns (RateLimiter);
}
message Squad {
int32 id = 1;
string name = 2;
int64 created_at = 3;
int64 updated_at = 4;
}
message SquadCreate {
string name = 1;
}
message SquadUpdate {
string name = 1;
}
message SquadList {
repeated Squad values = 1;
}
message SquadUpdateRequest {
int32 id = 1;
SquadUpdate update = 2;
}
message Node {
string uuid = 1;
string name = 2;
repeated int32 squad_ids = 3;
int64 created_at = 4;
int64 updated_at = 5;
}
message NodeCreate {
string uuid = 1;
string name = 2;
repeated int32 squad_ids = 3;
}
message NodeUpdate {
string name = 1;
}
message NodeList {
repeated Node values = 1;
}
message NodeUpdateRequest {
string uuid = 1;
NodeUpdate update = 2;
}
message User {
int32 id = 1;
repeated int32 squad_ids = 2;
string username = 3;
string inbound = 4;
string type = 5;
string uuid = 6;
string password = 7;
string secret = 8;
string flow = 9;
int32 alter_id = 10;
int64 created_at = 11;
int64 updated_at = 12;
}
message UserCreate {
repeated int32 squad_ids = 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 UserUpdate {
string uuid = 1;
string password = 2;
string secret = 3;
string flow = 4;
int32 alter_id = 5;
}
message UserList {
repeated User values = 1;
}
message UserUpdateRequest {
int32 id = 1;
UserUpdate update = 2;
}
message BandwidthLimiter {
int32 id = 1;
repeated int32 squad_ids = 2;
string username = 3;
string outbound = 4;
string strategy = 5;
string connection_type = 6;
string mode = 7;
repeated string flow_keys = 8;
string speed = 9;
uint64 raw_speed = 10;
int64 created_at = 11;
int64 updated_at = 12;
}
message BandwidthLimiterCreate {
repeated int32 squad_ids = 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;
}
message BandwidthLimiterUpdate {
string username = 1;
string outbound = 2;
string strategy = 3;
string connection_type = 4;
string mode = 5;
repeated string flow_keys = 6;
string speed = 7;
}
message BandwidthLimiterList {
repeated BandwidthLimiter values = 1;
}
message BandwidthLimiterUpdateRequest {
int32 id = 1;
BandwidthLimiterUpdate update = 2;
}
message TrafficLimiter {
int32 id = 1;
repeated int32 squad_ids = 2;
string username = 3;
string outbound = 4;
string strategy = 5;
string mode = 6;
uint64 raw_used = 7;
string quota = 8;
uint64 raw_quota = 9;
int64 created_at = 10;
int64 updated_at = 11;
}
message TrafficLimiterCreate {
repeated int32 squad_ids = 1;
string username = 2;
string outbound = 3;
string strategy = 4;
string mode = 5;
string quota = 6;
}
message TrafficLimiterUpdate {
string username = 1;
string outbound = 2;
string strategy = 3;
string mode = 4;
string quota = 5;
}
message TrafficLimiterList {
repeated TrafficLimiter values = 1;
}
message TrafficLimiterUpdateRequest {
int32 id = 1;
TrafficLimiterUpdate update = 2;
}
message ConnectionLimiter {
int32 id = 1;
repeated int32 squad_ids = 2;
string username = 3;
string outbound = 4;
string strategy = 5;
string connection_type = 6;
string lock_type = 7;
uint32 count = 8;
int64 created_at = 9;
int64 updated_at = 10;
}
message ConnectionLimiterCreate {
repeated int32 squad_ids = 1;
string username = 2;
string outbound = 3;
string strategy = 4;
string connection_type = 5;
string lock_type = 6;
uint32 count = 7;
}
message ConnectionLimiterUpdate {
string username = 1;
string outbound = 2;
string strategy = 3;
string connection_type = 4;
string lock_type = 5;
uint32 count = 6;
}
message ConnectionLimiterList {
repeated ConnectionLimiter values = 1;
}
message ConnectionLimiterUpdateRequest {
int32 id = 1;
ConnectionLimiterUpdate update = 2;
}
message RateLimiter {
int32 id = 1;
repeated int32 squad_ids = 2;
string username = 3;
string outbound = 4;
string strategy = 5;
string connection_type = 6;
uint32 count = 7;
string interval = 8;
int64 created_at = 9;
int64 updated_at = 10;
}
message RateLimiterCreate {
repeated int32 squad_ids = 1;
string username = 2;
string outbound = 3;
string strategy = 4;
string connection_type = 5;
uint32 count = 6;
string interval = 7;
}
message RateLimiterUpdate {
string username = 1;
string outbound = 2;
string strategy = 3;
string connection_type = 4;
uint32 count = 5;
string interval = 6;
}
message RateLimiterList {
repeated RateLimiter values = 1;
}
message RateLimiterUpdateRequest {
int32 id = 1;
RateLimiterUpdate update = 2;
}
message IdRequest {
int32 id = 1;
}
message UuidRequest {
string uuid = 1;
}
message CountReply {
int64 count = 1;
}
message NodeStatusReply {
string status = 1;
}
message StringList {
repeated string values = 1;
}
message Filters {
map<string, StringList> values = 1;
}
message Empty {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,137 @@
package server
import (
CM "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/manager_api/grpc/manager"
)
func toIntSlice(values []int32) []int {
out := make([]int, len(values))
for i, v := range values {
out[i] = int(v)
}
return out
}
func toInt32Slice(values []int) []int32 {
out := make([]int32, len(values))
for i, v := range values {
out[i] = int32(v)
}
return out
}
func convertSquad(v CM.Squad) *pb.Squad {
return &pb.Squad{
Id: int32(v.ID),
Name: v.Name,
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertNode(v CM.Node) *pb.Node {
return &pb.Node{
Uuid: v.UUID,
Name: v.Name,
SquadIds: toInt32Slice(v.SquadIDs),
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertUser(v CM.User) *pb.User {
return &pb.User{
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Inbound: v.Inbound,
Type: v.Type,
Uuid: v.UUID,
Password: v.Password,
Secret: v.Secret,
Flow: v.Flow,
AlterId: int32(v.AlterID),
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertBandwidthLimiter(v CM.BandwidthLimiter) *pb.BandwidthLimiter {
return &pb.BandwidthLimiter{
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Outbound: v.Outbound,
Strategy: v.Strategy,
ConnectionType: v.ConnectionType,
Mode: v.Mode,
FlowKeys: v.FlowKeys,
Speed: v.Speed,
RawSpeed: v.RawSpeed,
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertTrafficLimiter(v CM.TrafficLimiter) *pb.TrafficLimiter {
return &pb.TrafficLimiter{
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Outbound: v.Outbound,
Strategy: v.Strategy,
Mode: v.Mode,
RawUsed: v.RawUsed,
Quota: v.Quota,
RawQuota: v.RawQuota,
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertConnectionLimiter(v CM.ConnectionLimiter) *pb.ConnectionLimiter {
return &pb.ConnectionLimiter{
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Outbound: v.Outbound,
Strategy: v.Strategy,
ConnectionType: v.ConnectionType,
LockType: v.LockType,
Count: v.Count,
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertRateLimiter(v CM.RateLimiter) *pb.RateLimiter {
return &pb.RateLimiter{
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Outbound: v.Outbound,
Strategy: v.Strategy,
ConnectionType: v.ConnectionType,
Count: v.Count,
Interval: v.Interval,
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
func convertFilters(req *pb.Filters) map[string][]string {
filters := map[string][]string{}
for k, v := range req.GetValues() {
filters[k] = append([]string(nil), v.GetValues()...)
}
return filters
}
func convertListFilters(req *pb.Filters) map[string][]string {
filters := convertFilters(req)
if _, ok := filters["limit"]; !ok {
filters["limit"] = []string{"100"}
}
return filters
}

View File

@@ -0,0 +1,465 @@
package server
import (
"context"
CM "github.com/sagernet/sing-box/service/manager/constant"
pb "github.com/sagernet/sing-box/service/manager_api/grpc/manager"
)
func (s *Server) CreateSquad(_ context.Context, req *pb.SquadCreate) (*pb.Squad, error) {
v, err := s.manager.CreateSquad(CM.SquadCreate{Name: req.GetName()})
if err != nil {
return nil, err
}
return convertSquad(v), nil
}
func (s *Server) GetSquads(_ context.Context, req *pb.Filters) (*pb.SquadList, error) {
items, err := s.manager.GetSquads(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.Squad, len(items))
for i, v := range items {
out[i] = convertSquad(v)
}
return &pb.SquadList{Values: out}, nil
}
func (s *Server) GetSquadsCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetSquadsCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetSquad(_ context.Context, req *pb.IdRequest) (*pb.Squad, error) {
v, err := s.manager.GetSquad(int(req.GetId()))
if err != nil {
return nil, err
}
return convertSquad(v), nil
}
func (s *Server) UpdateSquad(_ context.Context, req *pb.SquadUpdateRequest) (*pb.Squad, error) {
v, err := s.manager.UpdateSquad(int(req.GetId()), CM.SquadUpdate{Name: req.GetUpdate().GetName()})
if err != nil {
return nil, err
}
return convertSquad(v), nil
}
func (s *Server) DeleteSquad(_ context.Context, req *pb.IdRequest) (*pb.Squad, error) {
v, err := s.manager.DeleteSquad(int(req.GetId()))
if err != nil {
return nil, err
}
return convertSquad(v), nil
}
func (s *Server) CreateNode(_ context.Context, req *pb.NodeCreate) (*pb.Node, error) {
v, err := s.manager.CreateNode(CM.NodeCreate{
UUID: req.GetUuid(),
Name: req.GetName(),
SquadIDs: toIntSlice(req.GetSquadIds()),
})
if err != nil {
return nil, err
}
return convertNode(v), nil
}
func (s *Server) GetNodes(_ context.Context, req *pb.Filters) (*pb.NodeList, error) {
items, err := s.manager.GetNodes(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.Node, len(items))
for i, v := range items {
out[i] = convertNode(v)
}
return &pb.NodeList{Values: out}, nil
}
func (s *Server) GetNodesCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetNodesCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetNode(_ context.Context, req *pb.UuidRequest) (*pb.Node, error) {
v, err := s.manager.GetNode(req.GetUuid())
if err != nil {
return nil, err
}
return convertNode(v), nil
}
func (s *Server) GetNodeStatus(_ context.Context, req *pb.UuidRequest) (*pb.NodeStatusReply, error) {
status, err := s.manager.GetNodeStatus(req.GetUuid())
if err != nil {
return nil, err
}
return &pb.NodeStatusReply{Status: status}, nil
}
func (s *Server) UpdateNode(_ context.Context, req *pb.NodeUpdateRequest) (*pb.Node, error) {
v, err := s.manager.UpdateNode(req.GetUuid(), CM.NodeUpdate{Name: req.GetUpdate().GetName()})
if err != nil {
return nil, err
}
return convertNode(v), nil
}
func (s *Server) DeleteNode(_ context.Context, req *pb.UuidRequest) (*pb.Node, error) {
v, err := s.manager.DeleteNode(req.GetUuid())
if err != nil {
return nil, err
}
return convertNode(v), nil
}
func (s *Server) CreateUser(_ context.Context, req *pb.UserCreate) (*pb.User, error) {
v, err := s.manager.CreateUser(CM.UserCreate{
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Inbound: req.GetInbound(),
Type: req.GetType(),
UUID: req.GetUuid(),
Password: req.GetPassword(),
Secret: req.GetSecret(),
Flow: req.GetFlow(),
AlterID: int(req.GetAlterId()),
})
if err != nil {
return nil, err
}
return convertUser(v), nil
}
func (s *Server) GetUsers(_ context.Context, req *pb.Filters) (*pb.UserList, error) {
items, err := s.manager.GetUsers(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.User, len(items))
for i, v := range items {
out[i] = convertUser(v)
}
return &pb.UserList{Values: out}, nil
}
func (s *Server) GetUsersCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetUsersCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetUser(_ context.Context, req *pb.IdRequest) (*pb.User, error) {
v, err := s.manager.GetUser(int(req.GetId()))
if err != nil {
return nil, err
}
return convertUser(v), nil
}
func (s *Server) UpdateUser(_ context.Context, req *pb.UserUpdateRequest) (*pb.User, error) {
u := req.GetUpdate()
v, err := s.manager.UpdateUser(int(req.GetId()), CM.UserUpdate{
UUID: u.GetUuid(),
Password: u.GetPassword(),
Secret: u.GetSecret(),
Flow: u.GetFlow(),
AlterID: int(u.GetAlterId()),
})
if err != nil {
return nil, err
}
return convertUser(v), nil
}
func (s *Server) DeleteUser(_ context.Context, req *pb.IdRequest) (*pb.User, error) {
v, err := s.manager.DeleteUser(int(req.GetId()))
if err != nil {
return nil, err
}
return convertUser(v), nil
}
func (s *Server) CreateBandwidthLimiter(_ context.Context, req *pb.BandwidthLimiterCreate) (*pb.BandwidthLimiter, error) {
v, err := s.manager.CreateBandwidthLimiter(CM.BandwidthLimiterCreate{
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Outbound: req.GetOutbound(),
Strategy: req.GetStrategy(),
ConnectionType: req.GetConnectionType(),
Mode: req.GetMode(),
FlowKeys: req.GetFlowKeys(),
Speed: req.GetSpeed(),
})
if err != nil {
return nil, err
}
return convertBandwidthLimiter(v), nil
}
func (s *Server) GetBandwidthLimiters(_ context.Context, req *pb.Filters) (*pb.BandwidthLimiterList, error) {
items, err := s.manager.GetBandwidthLimiters(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.BandwidthLimiter, len(items))
for i, v := range items {
out[i] = convertBandwidthLimiter(v)
}
return &pb.BandwidthLimiterList{Values: out}, nil
}
func (s *Server) GetBandwidthLimitersCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetBandwidthLimitersCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetBandwidthLimiter(_ context.Context, req *pb.IdRequest) (*pb.BandwidthLimiter, error) {
v, err := s.manager.GetBandwidthLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertBandwidthLimiter(v), nil
}
func (s *Server) UpdateBandwidthLimiter(_ context.Context, req *pb.BandwidthLimiterUpdateRequest) (*pb.BandwidthLimiter, error) {
u := req.GetUpdate()
v, err := s.manager.UpdateBandwidthLimiter(int(req.GetId()), CM.BandwidthLimiterUpdate{
Username: u.GetUsername(),
Outbound: u.GetOutbound(),
Strategy: u.GetStrategy(),
ConnectionType: u.GetConnectionType(),
Mode: u.GetMode(),
FlowKeys: u.GetFlowKeys(),
Speed: u.GetSpeed(),
})
if err != nil {
return nil, err
}
return convertBandwidthLimiter(v), nil
}
func (s *Server) DeleteBandwidthLimiter(_ context.Context, req *pb.IdRequest) (*pb.BandwidthLimiter, error) {
v, err := s.manager.DeleteBandwidthLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertBandwidthLimiter(v), nil
}
func (s *Server) CreateTrafficLimiter(_ context.Context, req *pb.TrafficLimiterCreate) (*pb.TrafficLimiter, error) {
v, err := s.manager.CreateTrafficLimiter(CM.TrafficLimiterCreate{
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Outbound: req.GetOutbound(),
Strategy: req.GetStrategy(),
Mode: req.GetMode(),
Quota: req.GetQuota(),
})
if err != nil {
return nil, err
}
return convertTrafficLimiter(v), nil
}
func (s *Server) GetTrafficLimiters(_ context.Context, req *pb.Filters) (*pb.TrafficLimiterList, error) {
items, err := s.manager.GetTrafficLimiters(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.TrafficLimiter, len(items))
for i, v := range items {
out[i] = convertTrafficLimiter(v)
}
return &pb.TrafficLimiterList{Values: out}, nil
}
func (s *Server) GetTrafficLimitersCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetTrafficLimitersCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetTrafficLimiter(_ context.Context, req *pb.IdRequest) (*pb.TrafficLimiter, error) {
v, err := s.manager.GetTrafficLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertTrafficLimiter(v), nil
}
func (s *Server) UpdateTrafficLimiter(_ context.Context, req *pb.TrafficLimiterUpdateRequest) (*pb.TrafficLimiter, error) {
u := req.GetUpdate()
v, err := s.manager.UpdateTrafficLimiter(int(req.GetId()), CM.TrafficLimiterUpdate{
Username: u.GetUsername(),
Outbound: u.GetOutbound(),
Strategy: u.GetStrategy(),
Mode: u.GetMode(),
Quota: u.GetQuota(),
})
if err != nil {
return nil, err
}
return convertTrafficLimiter(v), nil
}
func (s *Server) DeleteTrafficLimiter(_ context.Context, req *pb.IdRequest) (*pb.TrafficLimiter, error) {
v, err := s.manager.DeleteTrafficLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertTrafficLimiter(v), nil
}
func (s *Server) CreateConnectionLimiter(_ context.Context, req *pb.ConnectionLimiterCreate) (*pb.ConnectionLimiter, error) {
v, err := s.manager.CreateConnectionLimiter(CM.ConnectionLimiterCreate{
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Outbound: req.GetOutbound(),
Strategy: req.GetStrategy(),
ConnectionType: req.GetConnectionType(),
LockType: req.GetLockType(),
Count: req.GetCount(),
})
if err != nil {
return nil, err
}
return convertConnectionLimiter(v), nil
}
func (s *Server) GetConnectionLimiters(_ context.Context, req *pb.Filters) (*pb.ConnectionLimiterList, error) {
items, err := s.manager.GetConnectionLimiters(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.ConnectionLimiter, len(items))
for i, v := range items {
out[i] = convertConnectionLimiter(v)
}
return &pb.ConnectionLimiterList{Values: out}, nil
}
func (s *Server) GetConnectionLimitersCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetConnectionLimitersCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetConnectionLimiter(_ context.Context, req *pb.IdRequest) (*pb.ConnectionLimiter, error) {
v, err := s.manager.GetConnectionLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertConnectionLimiter(v), nil
}
func (s *Server) UpdateConnectionLimiter(_ context.Context, req *pb.ConnectionLimiterUpdateRequest) (*pb.ConnectionLimiter, error) {
u := req.GetUpdate()
v, err := s.manager.UpdateConnectionLimiter(int(req.GetId()), CM.ConnectionLimiterUpdate{
Username: u.GetUsername(),
Outbound: u.GetOutbound(),
Strategy: u.GetStrategy(),
ConnectionType: u.GetConnectionType(),
LockType: u.GetLockType(),
Count: u.GetCount(),
})
if err != nil {
return nil, err
}
return convertConnectionLimiter(v), nil
}
func (s *Server) DeleteConnectionLimiter(_ context.Context, req *pb.IdRequest) (*pb.ConnectionLimiter, error) {
v, err := s.manager.DeleteConnectionLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertConnectionLimiter(v), nil
}
func (s *Server) CreateRateLimiter(_ context.Context, req *pb.RateLimiterCreate) (*pb.RateLimiter, error) {
v, err := s.manager.CreateRateLimiter(CM.RateLimiterCreate{
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Outbound: req.GetOutbound(),
Strategy: req.GetStrategy(),
ConnectionType: req.GetConnectionType(),
Count: req.GetCount(),
Interval: req.GetInterval(),
})
if err != nil {
return nil, err
}
return convertRateLimiter(v), nil
}
func (s *Server) GetRateLimiters(_ context.Context, req *pb.Filters) (*pb.RateLimiterList, error) {
items, err := s.manager.GetRateLimiters(convertListFilters(req))
if err != nil {
return nil, err
}
out := make([]*pb.RateLimiter, len(items))
for i, v := range items {
out[i] = convertRateLimiter(v)
}
return &pb.RateLimiterList{Values: out}, nil
}
func (s *Server) GetRateLimitersCount(_ context.Context, req *pb.Filters) (*pb.CountReply, error) {
n, err := s.manager.GetRateLimitersCount(convertFilters(req))
if err != nil {
return nil, err
}
return &pb.CountReply{Count: int64(n)}, nil
}
func (s *Server) GetRateLimiter(_ context.Context, req *pb.IdRequest) (*pb.RateLimiter, error) {
v, err := s.manager.GetRateLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertRateLimiter(v), nil
}
func (s *Server) UpdateRateLimiter(_ context.Context, req *pb.RateLimiterUpdateRequest) (*pb.RateLimiter, error) {
u := req.GetUpdate()
v, err := s.manager.UpdateRateLimiter(int(req.GetId()), CM.RateLimiterUpdate{
Username: u.GetUsername(),
Outbound: u.GetOutbound(),
Strategy: u.GetStrategy(),
ConnectionType: u.GetConnectionType(),
Count: u.GetCount(),
Interval: u.GetInterval(),
})
if err != nil {
return nil, err
}
return convertRateLimiter(v), nil
}
func (s *Server) DeleteRateLimiter(_ context.Context, req *pb.IdRequest) (*pb.RateLimiter, error) {
v, err := s.manager.DeleteRateLimiter(int(req.GetId()))
if err != nil {
return nil, err
}
return convertRateLimiter(v), nil
}

View File

@@ -0,0 +1,157 @@
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/manager_api/grpc/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 Server 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.ManagerAPIServerOptions
mtx sync.Mutex
}
func NewServer(ctx context.Context, logger log.ContextLogger, tag string, options option.ManagerAPIServerOptions) (*Server, error) {
if options.APIKey == "" {
return nil, E.New("missing api key")
}
return &Server{
Adapter: boxService.NewAdapter(C.TypeManagerAPI, 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 *Server) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
managerService, ok := boxManager.Get(s.options.Manager)
if !ok {
return E.New("manager ", s.options.Manager, " not found")
}
s.manager, ok = managerService.(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 {
if err := s.tlsConfig.Start(); 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, unaryErrorInterceptor),
grpc.StreamInterceptor(s.streamAuthInterceptor),
)
pb.RegisterManagerServer(s.grpcServer, s)
go func() {
if err := s.grpcServer.Serve(tcpListener); err != nil && !errors.Is(err, grpc.ErrServerStopped) {
s.logger.Error("serve error: ", err)
}
}()
return nil
}
func (s *Server) Close() error {
if s.grpcServer != nil {
s.grpcServer.GracefulStop()
}
return common.Close(
common.PtrOrNil(s.listener),
s.tlsConfig,
)
}
func (s *Server) 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 *Server) 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 unaryErrorInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
resp, err := handler(ctx, req)
if err == CM.ErrNotFound {
return resp, status.Error(codes.NotFound, err.Error())
}
return resp, err
}
func (s *Server) 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
}