mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-21 18:29:13 +03:00
Add ssm api server
This commit is contained in:
24
experimental/ssmapi.go
Normal file
24
experimental/ssmapi.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package experimental
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type SSMServerConstructor = func(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error)
|
||||
|
||||
var ssmServerConstructor SSMServerConstructor
|
||||
|
||||
func RegisterSSMServerConstructor(constructor SSMServerConstructor) {
|
||||
ssmServerConstructor = constructor
|
||||
}
|
||||
|
||||
func NewSSMServer(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error) {
|
||||
if ssmServerConstructor == nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return ssmServerConstructor(router, logger, options)
|
||||
}
|
||||
214
experimental/ssmapi/api.go
Normal file
214
experimental/ssmapi/api.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func (s *Server) setupRoutes(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get("/", s.getServerInfo)
|
||||
|
||||
r.Get("/nodes", s.getNodes)
|
||||
r.Post("/nodes", s.addNode)
|
||||
r.Get("/nodes/{id}", s.getNode)
|
||||
r.Put("/nodes/{id}", s.updateNode)
|
||||
r.Delete("/nodes/{id}", s.deleteNode)
|
||||
|
||||
r.Get("/users", s.listUser)
|
||||
r.Post("/users", s.addUser)
|
||||
r.Get("/users/{username}", s.getUser)
|
||||
r.Put("/users/{username}", s.updateUser)
|
||||
r.Delete("/users/{username}", s.deleteUser)
|
||||
|
||||
r.Get("/stats", s.getStats)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) getServerInfo(writer http.ResponseWriter, request *http.Request) {
|
||||
render.JSON(writer, request, render.M{
|
||||
"server": "sing-box",
|
||||
"apiVersion": "v1",
|
||||
"_sing_box_version": C.Version,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) getNodes(writer http.ResponseWriter, request *http.Request) {
|
||||
var response struct {
|
||||
Protocols []string `json:"protocols"`
|
||||
Shadowsocks []ShadowsocksNodeObject `json:"shadowsocks,omitempty"`
|
||||
}
|
||||
for _, node := range s.nodes {
|
||||
if !common.Contains(response.Protocols, node.Protocol()) {
|
||||
response.Protocols = append(response.Protocols, node.Protocol())
|
||||
}
|
||||
switch node.Protocol() {
|
||||
case C.TypeShadowsocks:
|
||||
response.Shadowsocks = append(response.Shadowsocks, node.Shadowsocks())
|
||||
}
|
||||
}
|
||||
render.JSON(writer, request, &response)
|
||||
}
|
||||
|
||||
func (s *Server) addNode(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (s *Server) getNode(writer http.ResponseWriter, request *http.Request) {
|
||||
nodeID := chi.URLParam(request, "id")
|
||||
if nodeID == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
for _, node := range s.nodes {
|
||||
if nodeID == node.ID() {
|
||||
render.JSON(writer, request, render.M{
|
||||
node.Protocol(): node.Object(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) updateNode(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (s *Server) deleteNode(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
type SSMUserObject struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"uPSK,omitempty"`
|
||||
DownlinkBytes int64 `json:"downlinkBytes"`
|
||||
UplinkBytes int64 `json:"uplinkBytes"`
|
||||
|
||||
DownlinkPackets int64 `json:"downlinkPackets"`
|
||||
UplinkPackets int64 `json:"uplinkPackets"`
|
||||
TCPSessions int64 `json:"tcpSessions"`
|
||||
UDPSessions int64 `json:"udpSessions"`
|
||||
}
|
||||
|
||||
func (s *Server) listUser(writer http.ResponseWriter, request *http.Request) {
|
||||
render.JSON(writer, request, render.M{
|
||||
"users": s.userManager.List(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) addUser(writer http.ResponseWriter, request *http.Request) {
|
||||
var addRequest struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"uPSK"`
|
||||
}
|
||||
err := render.DecodeJSON(request.Body, &addRequest)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
err = s.userManager.Add(addRequest.UserName, addRequest.Password)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (s *Server) getUser(writer http.ResponseWriter, request *http.Request) {
|
||||
userName := chi.URLParam(request, "username")
|
||||
if userName == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
uPSK, loaded := s.userManager.Get(userName)
|
||||
if !loaded {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
user := SSMUserObject{
|
||||
UserName: userName,
|
||||
Password: uPSK,
|
||||
}
|
||||
s.trafficManager.ReadUser(&user)
|
||||
render.JSON(writer, request, user)
|
||||
}
|
||||
|
||||
func (s *Server) updateUser(writer http.ResponseWriter, request *http.Request) {
|
||||
userName := chi.URLParam(request, "username")
|
||||
if userName == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var updateRequest struct {
|
||||
Password string `json:"uPSK"`
|
||||
}
|
||||
err := render.DecodeJSON(request.Body, &updateRequest)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
_, loaded := s.userManager.Get(userName)
|
||||
if !loaded {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
err = s.userManager.Update(userName, updateRequest.Password)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) deleteUser(writer http.ResponseWriter, request *http.Request) {
|
||||
userName := chi.URLParam(request, "username")
|
||||
if userName == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, loaded := s.userManager.Get(userName)
|
||||
if !loaded {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
err := s.userManager.Delete(userName)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) getStats(writer http.ResponseWriter, request *http.Request) {
|
||||
requireClear := chi.URLParam(request, "clear") == "true"
|
||||
|
||||
users := s.userManager.List()
|
||||
s.trafficManager.ReadUsers(users)
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.trafficManager.ReadGlobal()
|
||||
|
||||
if requireClear {
|
||||
s.trafficManager.Clear()
|
||||
}
|
||||
|
||||
render.JSON(writer, request, render.M{
|
||||
"uplinkBytes": uplinkBytes,
|
||||
"downlinkBytes": downlinkBytes,
|
||||
"uplinkPackets": uplinkPackets,
|
||||
"downlinkPackets": downlinkPackets,
|
||||
"tcpSessions": tcpSessions,
|
||||
"udpSessions": udpSessions,
|
||||
"users": users,
|
||||
})
|
||||
}
|
||||
117
experimental/ssmapi/server.go
Normal file
117
experimental/ssmapi/server.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func init() {
|
||||
experimental.RegisterSSMServerConstructor(NewServer)
|
||||
}
|
||||
|
||||
var _ adapter.SSMServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
router adapter.Router
|
||||
logger log.Logger
|
||||
httpServer *http.Server
|
||||
tcpListener net.Listener
|
||||
|
||||
nodes []Node
|
||||
userManager *UserManager
|
||||
trafficManager *TrafficManager
|
||||
}
|
||||
|
||||
type Node interface {
|
||||
Protocol() string
|
||||
ID() string
|
||||
Shadowsocks() ShadowsocksNodeObject
|
||||
Object() any
|
||||
Tag() string
|
||||
UpdateUsers(users []string, uPSKs []string) error
|
||||
}
|
||||
|
||||
func NewServer(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error) {
|
||||
chiRouter := chi.NewRouter()
|
||||
server := &Server{
|
||||
router: router,
|
||||
logger: logger,
|
||||
httpServer: &http.Server{
|
||||
Addr: options.Listen,
|
||||
Handler: chiRouter,
|
||||
},
|
||||
nodes: make([]Node, 0, len(options.Nodes)),
|
||||
}
|
||||
for i, nodeOptions := range options.Nodes {
|
||||
switch nodeOptions.Type {
|
||||
case C.TypeShadowsocks:
|
||||
ssOptions := nodeOptions.ShadowsocksOptions
|
||||
inbound, loaded := router.Inbound(ssOptions.Inbound)
|
||||
if !loaded {
|
||||
return nil, E.New("parse SSM node[", i, "]: inbound", ssOptions.Inbound, "not found")
|
||||
}
|
||||
ssInbound, isSS := inbound.(adapter.ManagedShadowsocksServer)
|
||||
if !isSS {
|
||||
return nil, E.New("parse SSM node[", i, "]: inbound", ssOptions.Inbound, "is not a shadowsocks inbound")
|
||||
}
|
||||
node := &ShadowsocksNode{
|
||||
ssOptions,
|
||||
ssInbound,
|
||||
}
|
||||
server.nodes = append(server.nodes, node)
|
||||
}
|
||||
}
|
||||
server.trafficManager = NewTrafficManager(server.nodes)
|
||||
server.userManager = NewUserManager(server.nodes, server.trafficManager)
|
||||
listenPrefix := options.ListenPrefix
|
||||
if !strings.HasPrefix(listenPrefix, "/") {
|
||||
listenPrefix = "/" + listenPrefix
|
||||
}
|
||||
chiRouter.Route(listenPrefix+"server/v1", server.setupRoutes)
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.logger.Info("ssm-api started at ", listener.Addr())
|
||||
s.tcpListener = listener
|
||||
go func() {
|
||||
err = s.httpServer.Serve(listener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Error("ssm-api serve error: ", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.httpServer),
|
||||
s.tcpListener,
|
||||
s.trafficManager,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) RoutedConnection(metadata adapter.InboundContext, conn net.Conn) net.Conn {
|
||||
return s.trafficManager.RoutedConnection(metadata, conn)
|
||||
}
|
||||
|
||||
func (s *Server) RoutedPacketConnection(metadata adapter.InboundContext, conn N.PacketConn) N.PacketConn {
|
||||
return s.trafficManager.RoutedPacketConnection(metadata, conn)
|
||||
}
|
||||
54
experimental/ssmapi/shadowsocks.go
Normal file
54
experimental/ssmapi/shadowsocks.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
var _ Node = (*ShadowsocksNode)(nil)
|
||||
|
||||
type ShadowsocksNode struct {
|
||||
node option.SSMShadowsocksNode
|
||||
inbound adapter.ManagedShadowsocksServer
|
||||
}
|
||||
|
||||
type ShadowsocksNodeObject struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Passwords []string `json:"iPSKs,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
func (n *ShadowsocksNode) Protocol() string {
|
||||
return C.TypeShadowsocks
|
||||
}
|
||||
|
||||
func (n *ShadowsocksNode) ID() string {
|
||||
return n.node.ID
|
||||
}
|
||||
|
||||
func (n *ShadowsocksNode) Shadowsocks() ShadowsocksNodeObject {
|
||||
return ShadowsocksNodeObject{
|
||||
ID: n.node.ID,
|
||||
Name: n.node.Name,
|
||||
Endpoint: n.node.Address,
|
||||
Method: n.inbound.Method(),
|
||||
Passwords: []string{n.inbound.Password()},
|
||||
Tags: n.node.Tags,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *ShadowsocksNode) Object() any {
|
||||
return n.Shadowsocks()
|
||||
}
|
||||
|
||||
func (n *ShadowsocksNode) Tag() string {
|
||||
return n.inbound.Tag()
|
||||
}
|
||||
|
||||
func (n *ShadowsocksNode) UpdateUsers(users []string, uPSKs []string) error {
|
||||
return n.inbound.UpdateUsers(users, uPSKs)
|
||||
}
|
||||
227
experimental/ssmapi/traffic.go
Normal file
227
experimental/ssmapi/traffic.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/experimental/trackerconn"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type TrafficManager struct {
|
||||
nodeTags map[string]bool
|
||||
nodeUsers map[string]bool
|
||||
globalUplink *atomic.Int64
|
||||
globalDownlink *atomic.Int64
|
||||
globalUplinkPackets *atomic.Int64
|
||||
globalDownlinkPackets *atomic.Int64
|
||||
globalTCPSessions *atomic.Int64
|
||||
globalUDPSessions *atomic.Int64
|
||||
userAccess sync.Mutex
|
||||
userUplink map[string]*atomic.Int64
|
||||
userDownlink map[string]*atomic.Int64
|
||||
userUplinkPackets map[string]*atomic.Int64
|
||||
userDownlinkPackets map[string]*atomic.Int64
|
||||
userTCPSessions map[string]*atomic.Int64
|
||||
userUDPSessions map[string]*atomic.Int64
|
||||
}
|
||||
|
||||
func NewTrafficManager(nodes []Node) *TrafficManager {
|
||||
manager := &TrafficManager{
|
||||
nodeTags: make(map[string]bool),
|
||||
globalUplink: atomic.NewInt64(0),
|
||||
globalDownlink: atomic.NewInt64(0),
|
||||
globalUplinkPackets: atomic.NewInt64(0),
|
||||
globalDownlinkPackets: atomic.NewInt64(0),
|
||||
globalTCPSessions: atomic.NewInt64(0),
|
||||
globalUDPSessions: atomic.NewInt64(0),
|
||||
userUplink: make(map[string]*atomic.Int64),
|
||||
userDownlink: make(map[string]*atomic.Int64),
|
||||
userUplinkPackets: make(map[string]*atomic.Int64),
|
||||
userDownlinkPackets: make(map[string]*atomic.Int64),
|
||||
userTCPSessions: make(map[string]*atomic.Int64),
|
||||
userUDPSessions: make(map[string]*atomic.Int64),
|
||||
}
|
||||
for _, node := range nodes {
|
||||
manager.nodeTags[node.Tag()] = true
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
func (s *TrafficManager) UpdateUsers(users []string) {
|
||||
nodeUsers := make(map[string]bool)
|
||||
for _, user := range users {
|
||||
nodeUsers[user] = true
|
||||
}
|
||||
s.nodeUsers = nodeUsers
|
||||
}
|
||||
|
||||
func (s *TrafficManager) userCounter(user string) (*atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64) {
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
upCounter, loaded := s.userUplink[user]
|
||||
if !loaded {
|
||||
upCounter = atomic.NewInt64(0)
|
||||
s.userUplink[user] = upCounter
|
||||
}
|
||||
downCounter, loaded := s.userDownlink[user]
|
||||
if !loaded {
|
||||
downCounter = atomic.NewInt64(0)
|
||||
s.userDownlink[user] = downCounter
|
||||
}
|
||||
upPacketsCounter, loaded := s.userUplinkPackets[user]
|
||||
if !loaded {
|
||||
upPacketsCounter = atomic.NewInt64(0)
|
||||
s.userUplinkPackets[user] = upPacketsCounter
|
||||
}
|
||||
downPacketsCounter, loaded := s.userDownlinkPackets[user]
|
||||
if !loaded {
|
||||
downPacketsCounter = atomic.NewInt64(0)
|
||||
s.userDownlinkPackets[user] = downPacketsCounter
|
||||
}
|
||||
tcpSessionsCounter, loaded := s.userTCPSessions[user]
|
||||
if !loaded {
|
||||
tcpSessionsCounter = atomic.NewInt64(0)
|
||||
s.userTCPSessions[user] = tcpSessionsCounter
|
||||
}
|
||||
udpSessionsCounter, loaded := s.userUDPSessions[user]
|
||||
if !loaded {
|
||||
udpSessionsCounter = atomic.NewInt64(0)
|
||||
s.userUDPSessions[user] = udpSessionsCounter
|
||||
}
|
||||
return upCounter, downCounter, upPacketsCounter, downPacketsCounter, tcpSessionsCounter, udpSessionsCounter
|
||||
}
|
||||
|
||||
func createCounter(counterList []*atomic.Int64, packetCounterList []*atomic.Int64) func(n int64) {
|
||||
return func(n int64) {
|
||||
for _, counter := range counterList {
|
||||
counter.Add(n)
|
||||
}
|
||||
for _, counter := range packetCounterList {
|
||||
counter.Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TrafficManager) RoutedConnection(metadata adapter.InboundContext, conn net.Conn) net.Conn {
|
||||
s.globalTCPSessions.Inc()
|
||||
|
||||
var readCounter []*atomic.Int64
|
||||
var writeCounter []*atomic.Int64
|
||||
|
||||
if s.nodeTags[metadata.Inbound] {
|
||||
readCounter = append(readCounter, s.globalUplink)
|
||||
writeCounter = append(writeCounter, s.globalDownlink)
|
||||
}
|
||||
if s.nodeUsers[metadata.User] {
|
||||
upCounter, downCounter, _, _, tcpSessionCounter, _ := s.userCounter(metadata.User)
|
||||
readCounter = append(readCounter, upCounter)
|
||||
writeCounter = append(writeCounter, downCounter)
|
||||
tcpSessionCounter.Inc()
|
||||
}
|
||||
if len(readCounter) > 0 {
|
||||
return trackerconn.New(conn, readCounter, writeCounter)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (s *TrafficManager) RoutedPacketConnection(metadata adapter.InboundContext, conn N.PacketConn) N.PacketConn {
|
||||
s.globalUDPSessions.Inc()
|
||||
|
||||
var readCounter []*atomic.Int64
|
||||
var readPacketCounter []*atomic.Int64
|
||||
var writeCounter []*atomic.Int64
|
||||
var writePacketCounter []*atomic.Int64
|
||||
|
||||
if s.nodeTags[metadata.Inbound] {
|
||||
readCounter = append(readCounter, s.globalUplink)
|
||||
writeCounter = append(writeCounter, s.globalDownlink)
|
||||
readPacketCounter = append(readPacketCounter, s.globalUplinkPackets)
|
||||
writePacketCounter = append(writePacketCounter, s.globalDownlinkPackets)
|
||||
}
|
||||
if s.nodeUsers[metadata.User] {
|
||||
upCounter, downCounter, upPacketsCounter, downPacketsCounter, _, udpSessionCounter := s.userCounter(metadata.User)
|
||||
readCounter = append(readCounter, upCounter)
|
||||
writeCounter = append(writeCounter, downCounter)
|
||||
readPacketCounter = append(readPacketCounter, upPacketsCounter)
|
||||
writePacketCounter = append(writePacketCounter, downPacketsCounter)
|
||||
udpSessionCounter.Inc()
|
||||
}
|
||||
if len(readCounter) > 0 {
|
||||
return trackerconn.NewHookPacket(conn, createCounter(readCounter, readPacketCounter), createCounter(writeCounter, writePacketCounter))
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (s *TrafficManager) ReadUser(user *SSMUserObject) {
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
|
||||
s.readUser(user)
|
||||
}
|
||||
|
||||
func (s *TrafficManager) readUser(user *SSMUserObject) {
|
||||
if counter, loaded := s.userUplink[user.UserName]; loaded {
|
||||
user.UplinkBytes = counter.Load()
|
||||
}
|
||||
if counter, loaded := s.userDownlink[user.UserName]; loaded {
|
||||
user.DownlinkBytes = counter.Load()
|
||||
}
|
||||
if counter, loaded := s.userUplinkPackets[user.UserName]; loaded {
|
||||
user.UplinkPackets = counter.Load()
|
||||
}
|
||||
if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded {
|
||||
user.DownlinkPackets = counter.Load()
|
||||
}
|
||||
if counter, loaded := s.userTCPSessions[user.UserName]; loaded {
|
||||
user.TCPSessions = counter.Load()
|
||||
}
|
||||
if counter, loaded := s.userUDPSessions[user.UserName]; loaded {
|
||||
user.UDPSessions = counter.Load()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TrafficManager) ReadUsers(users []*SSMUserObject) {
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
for _, user := range users {
|
||||
s.readUser(user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TrafficManager) ReadGlobal() (
|
||||
uplinkBytes int64,
|
||||
downlinkBytes int64,
|
||||
uplinkPackets int64,
|
||||
downlinkPackets int64,
|
||||
tcpSessions int64,
|
||||
udpSessions int64,
|
||||
) {
|
||||
return s.globalUplink.Load(),
|
||||
s.globalDownlink.Load(),
|
||||
s.globalUplinkPackets.Load(),
|
||||
s.globalDownlinkPackets.Load(),
|
||||
s.globalTCPSessions.Load(),
|
||||
s.globalUDPSessions.Load()
|
||||
}
|
||||
|
||||
func (s *TrafficManager) Clear() {
|
||||
s.globalUplink.Store(0)
|
||||
s.globalDownlink.Store(0)
|
||||
s.globalUplinkPackets.Store(0)
|
||||
s.globalDownlinkPackets.Store(0)
|
||||
s.globalTCPSessions.Store(0)
|
||||
s.globalUDPSessions.Store(0)
|
||||
s.userAccess.Lock()
|
||||
defer s.userAccess.Unlock()
|
||||
s.userUplink = make(map[string]*atomic.Int64)
|
||||
s.userDownlink = make(map[string]*atomic.Int64)
|
||||
s.userUplinkPackets = make(map[string]*atomic.Int64)
|
||||
s.userDownlinkPackets = make(map[string]*atomic.Int64)
|
||||
s.userTCPSessions = make(map[string]*atomic.Int64)
|
||||
s.userUDPSessions = make(map[string]*atomic.Int64)
|
||||
}
|
||||
86
experimental/ssmapi/user.go
Normal file
86
experimental/ssmapi/user.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type UserManager struct {
|
||||
access sync.Mutex
|
||||
usersMap map[string]string
|
||||
nodes []Node
|
||||
trafficManager *TrafficManager
|
||||
}
|
||||
|
||||
func NewUserManager(nodes []Node, trafficManager *TrafficManager) *UserManager {
|
||||
return &UserManager{
|
||||
usersMap: make(map[string]string),
|
||||
nodes: nodes,
|
||||
trafficManager: trafficManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserManager) postUpdate() error {
|
||||
users := make([]string, 0, len(m.usersMap))
|
||||
uPSKs := make([]string, 0, len(m.usersMap))
|
||||
for username, password := range m.usersMap {
|
||||
users = append(users, username)
|
||||
uPSKs = append(uPSKs, password)
|
||||
}
|
||||
for _, node := range m.nodes {
|
||||
err := node.UpdateUsers(users, uPSKs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
m.trafficManager.UpdateUsers(users)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UserManager) List() []*SSMUserObject {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
users := make([]*SSMUserObject, 0, len(m.usersMap))
|
||||
for username, password := range m.usersMap {
|
||||
users = append(users, &SSMUserObject{
|
||||
UserName: username,
|
||||
Password: password,
|
||||
})
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func (m *UserManager) Add(username string, password string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if _, found := m.usersMap[username]; found {
|
||||
return E.New("user", username, "already exists")
|
||||
}
|
||||
m.usersMap[username] = password
|
||||
return m.postUpdate()
|
||||
}
|
||||
|
||||
func (m *UserManager) Get(username string) (string, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if password, found := m.usersMap[username]; found {
|
||||
return password, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (m *UserManager) Update(username string, password string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.usersMap[username] = password
|
||||
return m.postUpdate()
|
||||
}
|
||||
|
||||
func (m *UserManager) Delete(username string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
delete(m.usersMap, username)
|
||||
return m.postUpdate()
|
||||
}
|
||||
Reference in New Issue
Block a user