mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
Compare commits
6 Commits
dev-ssm-ap
...
v1.2-beta3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73c068b96f | ||
|
|
f516026540 | ||
|
|
3c5bc842ed | ||
|
|
d8270a66f4 | ||
|
|
123c383eae | ||
|
|
67814faf92 |
6
.github/workflows/mkdocs.yml
vendored
6
.github/workflows/mkdocs.yml
vendored
@@ -14,5 +14,7 @@ jobs:
|
|||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: pip install mkdocs-material mkdocs-static-i18n
|
- run: |
|
||||||
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
|
||||||
|
- run: |
|
||||||
|
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||||
@@ -17,7 +17,6 @@ builds:
|
|||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_ssm_api
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
@@ -51,7 +50,6 @@ builds:
|
|||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_ssm_api
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
overrides:
|
overrides:
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,with_ssm_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
|
|||||||
@@ -48,16 +48,3 @@ type V2RayStatsService interface {
|
|||||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSMServer interface {
|
|
||||||
Service
|
|
||||||
RoutedConnection(metadata InboundContext, conn net.Conn) net.Conn
|
|
||||||
RoutedPacketConnection(metadata InboundContext, conn N.PacketConn) N.PacketConn
|
|
||||||
}
|
|
||||||
|
|
||||||
type ManagedShadowsocksServer interface {
|
|
||||||
Inbound
|
|
||||||
Method() string
|
|
||||||
Password() string
|
|
||||||
UpdateUsers(users []string, uPSKs []string) error
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Service
|
||||||
|
|
||||||
Inbound(tag string) (Inbound, bool)
|
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
@@ -46,9 +45,6 @@ type Router interface {
|
|||||||
|
|
||||||
V2RayServer() V2RayServer
|
V2RayServer() V2RayServer
|
||||||
SetV2RayServer(server V2RayServer)
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
SSMServer() SSMServer
|
|
||||||
SetSSMServer(server SSMServer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
type routerContextKey struct{}
|
||||||
|
|||||||
21
box.go
21
box.go
@@ -32,7 +32,6 @@ type Box struct {
|
|||||||
logFile *os.File
|
logFile *os.File
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
v2rayServer adapter.V2RayServer
|
v2rayServer adapter.V2RayServer
|
||||||
ssmServer adapter.SSMServer
|
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +41,6 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
var needSSMAPI bool
|
|
||||||
if options.Experimental != nil {
|
if options.Experimental != nil {
|
||||||
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
||||||
needClashAPI = true
|
needClashAPI = true
|
||||||
@@ -50,9 +48,6 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
if options.Experimental.SSMAPI != nil && options.Experimental.SSMAPI.Listen != "" {
|
|
||||||
needSSMAPI = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var logFactory log.Factory
|
var logFactory log.Factory
|
||||||
@@ -161,7 +156,6 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
|
|
||||||
var clashServer adapter.ClashServer
|
var clashServer adapter.ClashServer
|
||||||
var v2rayServer adapter.V2RayServer
|
var v2rayServer adapter.V2RayServer
|
||||||
var ssmServer adapter.SSMServer
|
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -176,13 +170,6 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
router.SetV2RayServer(v2rayServer)
|
||||||
}
|
}
|
||||||
if needSSMAPI {
|
|
||||||
ssmServer, err = experimental.NewSSMServer(router, logFactory.NewLogger("ssm-api"), common.PtrValueOrDefault(options.Experimental.SSMAPI))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create ssm api server")
|
|
||||||
}
|
|
||||||
router.SetSSMServer(ssmServer)
|
|
||||||
}
|
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
inbounds: inbounds,
|
inbounds: inbounds,
|
||||||
@@ -193,7 +180,6 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
clashServer: clashServer,
|
clashServer: clashServer,
|
||||||
v2rayServer: v2rayServer,
|
v2rayServer: v2rayServer,
|
||||||
ssmServer: ssmServer,
|
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -258,12 +244,6 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "start v2ray api server")
|
return E.Cause(err, "start v2ray api server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.ssmServer != nil {
|
|
||||||
err = s.ssmServer.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ssm api server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -286,7 +266,6 @@ func (s *Box) Close() error {
|
|||||||
s.logFactory,
|
s.logFactory,
|
||||||
s.clashServer,
|
s.clashServer,
|
||||||
s.v2rayServer,
|
s.v2rayServer,
|
||||||
s.ssmServer,
|
|
||||||
common.PtrOrNil(s.logFile),
|
common.PtrOrNil(s.logFile),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
VersionDraft29 = 0xff00001d
|
VersionDraft29 = 0xff00001d
|
||||||
Version1 = 0x1
|
Version1 = 0x1
|
||||||
Version2 = 0x709a50c4
|
Version2 = 0x6b3343cf
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||||
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||||
SaltV2 = []byte{0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}
|
SaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
var Version = "1.2-beta2"
|
var Version = "1.2-beta3"
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#### 1.2-beta3
|
||||||
|
|
||||||
|
* Update QUIC v2 version number and initial salt
|
||||||
|
* Fix shadowtls v3 implementation
|
||||||
|
|
||||||
#### 1.2-beta2
|
#### 1.2-beta2
|
||||||
|
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
... // Listen Fields
|
... // Listen Fields
|
||||||
|
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
@@ -32,12 +32,13 @@ ShadowTLS protocol version.
|
|||||||
|---------------|-----------------------------------------------------------------------------------------|
|
|---------------|-----------------------------------------------------------------------------------------|
|
||||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||||
|
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||||
|
|
||||||
#### password
|
#### password
|
||||||
|
|
||||||
Set password.
|
Set password.
|
||||||
|
|
||||||
Only available in the ShadowTLS v2 protocol.
|
Only available in the ShadowTLS v2/v3 protocol.
|
||||||
|
|
||||||
#### handshake
|
#### handshake
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
... // 监听字段
|
... // 监听字段
|
||||||
|
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
@@ -32,12 +32,13 @@ ShadowTLS 协议版本。
|
|||||||
|---------------|-----------------------------------------------------------------------------------------|
|
|---------------|-----------------------------------------------------------------------------------------|
|
||||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||||
|
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||||
|
|
||||||
#### password
|
#### password
|
||||||
|
|
||||||
设置密码。
|
设置密码。
|
||||||
|
|
||||||
仅在 ShadowTLS v2 协议中可用。
|
仅在 ShadowTLS v2/v3 协议中可用。
|
||||||
|
|
||||||
#### handshake
|
#### handshake
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"type": "shadowtls",
|
"type": "shadowtls",
|
||||||
"listen": "::",
|
"listen": "::",
|
||||||
"listen_port": 4443,
|
"listen_port": 4443,
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"tag": "shadowtls-out",
|
"tag": "shadowtls-out",
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 4443,
|
"server_port": 4443,
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"tls": {
|
"tls": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
if len(options.Users) > 0 && len(options.Destinations) > 0 {
|
if len(options.Users) > 0 && len(options.Destinations) > 0 {
|
||||||
return nil, E.New("users and destinations options must not be combined")
|
return nil, E.New("users and destinations options must not be combined")
|
||||||
}
|
}
|
||||||
if len(options.Users) > 0 || options.Managed {
|
if len(options.Users) > 0 {
|
||||||
return newShadowsocksMulti(ctx, router, logger, tag, options)
|
return newShadowsocksMulti(ctx, router, logger, tag, options)
|
||||||
} else if len(options.Destinations) > 0 {
|
} else if len(options.Destinations) > 0 {
|
||||||
return newShadowsocksRelay(ctx, router, logger, tag, options)
|
return newShadowsocksRelay(ctx, router, logger, tag, options)
|
||||||
@@ -32,9 +32,8 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Inbound = (*Shadowsocks)(nil)
|
_ adapter.Inbound = (*Shadowsocks)(nil)
|
||||||
_ adapter.InjectableInbound = (*Shadowsocks)(nil)
|
_ adapter.InjectableInbound = (*Shadowsocks)(nil)
|
||||||
_ adapter.ManagedShadowsocksServer = (*Shadowsocks)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Shadowsocks struct {
|
type Shadowsocks struct {
|
||||||
@@ -77,18 +76,6 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Shadowsocks) Method() string {
|
|
||||||
return h.service.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Shadowsocks) Password() string {
|
|
||||||
return h.service.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Shadowsocks) UpdateUsers(names []string, uPSKs []string) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Inbound = (*ShadowsocksMulti)(nil)
|
_ adapter.Inbound = (*ShadowsocksMulti)(nil)
|
||||||
_ adapter.InjectableInbound = (*ShadowsocksMulti)(nil)
|
_ adapter.InjectableInbound = (*ShadowsocksMulti)(nil)
|
||||||
_ adapter.ManagedShadowsocksServer = (*ShadowsocksMulti)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowsocksMulti struct {
|
type ShadowsocksMulti struct {
|
||||||
@@ -62,13 +61,11 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(options.Users) > 0 {
|
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
|
||||||
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
|
return index
|
||||||
return index
|
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
|
||||||
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
|
return user.Password
|
||||||
return user.Password
|
}))
|
||||||
}))
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -78,29 +75,6 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowsocksMulti) Method() string {
|
|
||||||
return h.service.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ShadowsocksMulti) Password() string {
|
|
||||||
return h.service.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ShadowsocksMulti) UpdateUsers(users []string, uPSKs []string) error {
|
|
||||||
err := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int {
|
|
||||||
return index
|
|
||||||
}), uPSKs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.users = common.Map(users, func(user string) option.ShadowsocksUser {
|
|
||||||
return option.ShadowsocksUser{
|
|
||||||
Name: user,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Inbound = (*ShadowsocksRelay)(nil)
|
_ adapter.Inbound = (*ShadowsocksRelay)(nil)
|
||||||
_ adapter.InjectableInbound = (*ShadowsocksRelay)(nil)
|
_ adapter.InjectableInbound = (*ShadowsocksRelay)(nil)
|
||||||
_ adapter.ManagedShadowsocksServer = (*ShadowsocksRelay)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowsocksRelay struct {
|
type ShadowsocksRelay struct {
|
||||||
@@ -72,18 +71,6 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowsocksRelay) Method() string {
|
|
||||||
return h.service.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ShadowsocksRelay) Password() string {
|
|
||||||
return h.service.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ShadowsocksRelay) UpdateUsers(users []string, uPSKs []string) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build with_ssm_api
|
|
||||||
|
|
||||||
package include
|
|
||||||
|
|
||||||
import _ "github.com/sagernet/sing-box/experimental/ssmapi"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build !with_ssm_api
|
|
||||||
|
|
||||||
package include
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/experimental"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
experimental.RegisterSSMServerConstructor(func(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error) {
|
|
||||||
return nil, E.New(`SSM api is not included in this build, rebuild with -tags with_ssm_api`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,4 @@ package option
|
|||||||
type ExperimentalOptions struct {
|
type ExperimentalOptions struct {
|
||||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||||
SSMAPI *SSMAPIOptions `json:"ssm_api,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type ShadowsocksInboundOptions struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||||
Managed bool `json:"managed,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowsocksUser struct {
|
type ShadowsocksUser struct {
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SSMAPIOptions struct {
|
|
||||||
Listen string `json:"listen,omitempty"`
|
|
||||||
ListenPrefix string `json:"listen_prefix,omitempty"`
|
|
||||||
Nodes []SSMNode `json:"nodes,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type _SSMNode struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
ShadowsocksOptions SSMShadowsocksNode `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSMNode _SSMNode
|
|
||||||
|
|
||||||
func (h SSMNode) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch h.Type {
|
|
||||||
case C.TypeShadowsocks:
|
|
||||||
v = h.ShadowsocksOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown ssm node type: ", h.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_SSMNode)(h), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SSMNode) UnmarshalJSON(data []byte) error {
|
|
||||||
err := json.Unmarshal(data, (*_SSMNode)(h))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch h.Type {
|
|
||||||
case C.TypeShadowsocks:
|
|
||||||
v = &h.ShadowsocksOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown ssm node type: ", h.Type)
|
|
||||||
}
|
|
||||||
return UnmarshallExcluded(data, (*_SSMNode)(h), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSMShadowsocksNode struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
Inbound string `json:"inbound"`
|
|
||||||
}
|
|
||||||
@@ -56,8 +56,6 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
options.TLS.MaxVersion = "1.2"
|
options.TLS.MaxVersion = "1.2"
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
options.TLS.MinVersion = "1.3"
|
|
||||||
options.TLS.MaxVersion = "1.3"
|
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
|
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
curl -Lo go.tar.gz https://go.dev/dl/go1.20.linux-amd64.tar.gz
|
curl -Lo go.tar.gz https://go.dev/dl/go1.20.1.linux-amd64.tar.gz
|
||||||
sudo rm -rf /usr/local/go
|
sudo rm -rf /usr/local/go
|
||||||
sudo tar -C /usr/local -xzf go.tar.gz
|
sudo tar -C /usr/local -xzf go.tar.gz
|
||||||
rm go.tar.gz
|
rm go.tar.gz
|
||||||
@@ -97,7 +97,6 @@ type Router struct {
|
|||||||
processSearcher process.Searcher
|
processSearcher process.Searcher
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
v2rayServer adapter.V2RayServer
|
v2rayServer adapter.V2RayServer
|
||||||
ssmServer adapter.SSMServer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions, inbounds []option.Inbound) (*Router, error) {
|
func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions, inbounds []option.Inbound) (*Router, error) {
|
||||||
@@ -381,28 +380,10 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Inbound(tag string) (adapter.Inbound, bool) {
|
|
||||||
inbound, loaded := r.inboundByTag[tag]
|
|
||||||
return inbound, loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Outbounds() []adapter.Outbound {
|
func (r *Router) Outbounds() []adapter.Outbound {
|
||||||
return r.outbounds
|
return r.outbounds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
|
||||||
return outbound, loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) DefaultOutbound(network string) adapter.Outbound {
|
|
||||||
if network == N.NetworkTCP {
|
|
||||||
return r.defaultOutboundForConnection
|
|
||||||
} else {
|
|
||||||
return r.defaultOutboundForPacketConnection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Start() error {
|
func (r *Router) Start() error {
|
||||||
if r.needGeoIPDatabase {
|
if r.needGeoIPDatabase {
|
||||||
err := r.prepareGeoIPDatabase()
|
err := r.prepareGeoIPDatabase()
|
||||||
@@ -523,6 +504,19 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
|||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
|
return outbound, loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) DefaultOutbound(network string) adapter.Outbound {
|
||||||
|
if network == N.NetworkTCP {
|
||||||
|
return r.defaultOutboundForConnection
|
||||||
|
} else {
|
||||||
|
return r.defaultOutboundForPacketConnection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.InboundDetour != "" {
|
if metadata.InboundDetour != "" {
|
||||||
if metadata.LastInbound == metadata.InboundDetour {
|
if metadata.LastInbound == metadata.InboundDetour {
|
||||||
@@ -609,9 +603,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.ssmServer != nil {
|
|
||||||
conn = r.ssmServer.RoutedConnection(metadata, conn)
|
|
||||||
}
|
|
||||||
return detour.NewConnection(ctx, conn, metadata)
|
return detour.NewConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,9 +681,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.ssmServer != nil {
|
|
||||||
conn = r.ssmServer.RoutedPacketConnection(metadata, conn)
|
|
||||||
}
|
|
||||||
return detour.NewPacketConnection(ctx, conn, metadata)
|
return detour.NewPacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,14 +777,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
|
|||||||
r.v2rayServer = server
|
r.v2rayServer = server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) SSMServer() adapter.SSMServer {
|
|
||||||
return r.ssmServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) SetSSMServer(server adapter.SSMServer) {
|
|
||||||
r.ssmServer = server
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) {
|
|||||||
if err := config.SessionIDGenerator(hello.marshal(), hello.sessionId); err != nil {
|
if err := config.SessionIDGenerator(hello.marshal(), hello.sessionId); err != nil {
|
||||||
return nil, nil, errors.New("tls: generate session id failed: " + err.Error())
|
return nil, nil, errors.New("tls: generate session id failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
hello.raw = nil
|
||||||
} else {
|
} else {
|
||||||
if _, err := io.ReadFull(config.rand(), hello.sessionId); err != nil {
|
if _, err := io.ReadFull(config.rand(), hello.sessionId); err != nil {
|
||||||
return nil, nil, errors.New("tls: short read from Rand: " + err.Error())
|
return nil, nil, errors.New("tls: short read from Rand: " + err.Error())
|
||||||
|
|||||||
Reference in New Issue
Block a user