Skip to content

Commit 286439f

Browse files
committed
Add SSM API service
1 parent b85b80c commit 286439f

File tree

13 files changed

+726
-9
lines changed

13 files changed

+726
-9
lines changed

adapter/ssm.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package adapter
2+
3+
import (
4+
"net"
5+
6+
N "github.com/sagernet/sing/common/network"
7+
)
8+
9+
type ManagedSSMServer interface {
10+
Inbound
11+
SetTracker(tracker SSMTracker)
12+
UpdateUsers(users []string, uPSKs []string) error
13+
}
14+
15+
type SSMTracker interface {
16+
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
17+
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
18+
}

constant/proxy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
TypeTailscale = "tailscale"
2828
TypeDERP = "derp"
2929
TypeResolved = "resolved"
30+
TypeSSMAPI = "ssm-api"
3031
)
3132

3233
const (

docs/configuration/service/ssm-api.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
icon: material/new-box
3+
---
4+
5+
!!! question "Since sing-box 1.12.0"
6+
7+
# SSM API
8+
9+
SSM API service is a RESTful API server for managing Shadowsocks servers.
10+
11+
See https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md
12+
13+
### Structure
14+
15+
```json
16+
{
17+
"type": "ssm-api",
18+
19+
... // Listen Fields
20+
21+
"servers": {},
22+
"tls": {}
23+
}
24+
```
25+
26+
### Listen Fields
27+
28+
See [Listen Fields](/configuration/shared/listen/) for details.
29+
30+
### Fields
31+
32+
#### servers
33+
34+
==Required==
35+
36+
A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inbound/shadowsocks) tags.
37+
38+
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
39+
40+
Example:
41+
42+
```json
43+
{
44+
"servers": {
45+
"/": "ss-in"
46+
}
47+
}
48+
```
49+
50+
#### tls
51+
52+
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).

include/registry.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/sagernet/sing-box/protocol/vless"
3636
"github.com/sagernet/sing-box/protocol/vmess"
3737
"github.com/sagernet/sing-box/service/resolved"
38+
"github.com/sagernet/sing-box/service/ssmapi"
3839
E "github.com/sagernet/sing/common/exceptions"
3940
)
4041

@@ -125,6 +126,7 @@ func ServiceRegistry() *service.Registry {
125126
registry := service.NewRegistry()
126127

127128
resolved.RegisterService(registry)
129+
ssmapi.RegisterService(registry)
128130

129131
registerDERPService(registry)
130132

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ nav:
174174
- configuration/service/index.md
175175
- DERP: configuration/service/derp.md
176176
- Resolved: configuration/service/resolved.md
177+
- SSM API: configuration/service/ssm-api.md
177178
markdown_extensions:
178179
- pymdownx.inlinehilite
179180
- pymdownx.snippets

option/shadowsocks.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type ShadowsocksInboundOptions struct {
88
Users []ShadowsocksUser `json:"users,omitempty"`
99
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
1010
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
11+
Managed bool `json:"managed,omitempty"`
1112
}
1213

1314
type ShadowsocksUser struct {

option/ssmapi.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package option
2+
3+
import (
4+
"github.com/sagernet/sing/common/json/badjson"
5+
)
6+
7+
type SSMAPIServiceOptions struct {
8+
ListenOptions
9+
Servers *badjson.TypedMap[string, string] `json:"servers"`
10+
InboundTLSOptionsContainer
11+
}

protocol/shadowsocks/inbound.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ func RegisterInbound(registry *inbound.Registry) {
3232
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
3333
if len(options.Users) > 0 && len(options.Destinations) > 0 {
3434
return nil, E.New("users and destinations options must not be combined")
35+
} else if options.Managed && (len(options.Users) > 0 || len(options.Destinations) > 0) {
36+
return nil, E.New("users and destinations options are not supported in managed servers")
3537
}
36-
if len(options.Users) > 0 {
38+
if len(options.Users) > 0 || options.Managed {
3739
return newMultiInbound(ctx, router, logger, tag, options)
3840
} else if len(options.Destinations) > 0 {
3941
return newRelayInbound(ctx, router, logger, tag, options)

protocol/shadowsocks/inbound_multi.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ import (
2828
"github.com/sagernet/sing/common/ntp"
2929
)
3030

31-
var _ adapter.TCPInjectableInbound = (*MultiInbound)(nil)
31+
var (
32+
_ adapter.TCPInjectableInbound = (*MultiInbound)(nil)
33+
_ adapter.ManagedSSMServer = (*MultiInbound)(nil)
34+
)
3235

3336
type MultiInbound struct {
3437
inbound.Adapter
@@ -38,6 +41,7 @@ type MultiInbound struct {
3841
listener *listener.Listener
3942
service shadowsocks.MultiService[int]
4043
users []option.ShadowsocksUser
44+
tracker adapter.SSMTracker
4145
}
4246

4347
func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) {
@@ -79,13 +83,15 @@ func newMultiInbound(ctx context.Context, router adapter.Router, logger log.Cont
7983
if err != nil {
8084
return nil, err
8185
}
82-
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
83-
return index
84-
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
85-
return user.Password
86-
}))
87-
if err != nil {
88-
return nil, err
86+
if len(options.Users) > 0 {
87+
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
88+
return index
89+
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
90+
return user.Password
91+
}))
92+
if err != nil {
93+
return nil, err
94+
}
8995
}
9096
inbound.service = service
9197
inbound.users = options.Users
@@ -112,6 +118,25 @@ func (h *MultiInbound) Close() error {
112118
return h.listener.Close()
113119
}
114120

121+
func (h *MultiInbound) SetTracker(tracker adapter.SSMTracker) {
122+
h.tracker = tracker
123+
}
124+
125+
func (h *MultiInbound) UpdateUsers(users []string, uPSKs []string) error {
126+
err := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int {
127+
return index
128+
}), uPSKs)
129+
if err != nil {
130+
return err
131+
}
132+
h.users = common.Map(users, func(user string) option.ShadowsocksUser {
133+
return option.ShadowsocksUser{
134+
Name: user,
135+
}
136+
})
137+
return nil
138+
}
139+
115140
//nolint:staticcheck
116141
func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
117142
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
@@ -151,6 +176,9 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat
151176
metadata.InboundDetour = h.listener.ListenOptions().Detour
152177
//nolint:staticcheck
153178
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
179+
if h.tracker != nil {
180+
conn = h.tracker.TrackConnection(conn, metadata)
181+
}
154182
return h.router.RouteConnection(ctx, conn, metadata)
155183
}
156184

@@ -174,6 +202,9 @@ func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketCon
174202
metadata.InboundDetour = h.listener.ListenOptions().Detour
175203
//nolint:staticcheck
176204
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
205+
if h.tracker != nil {
206+
conn = h.tracker.TrackPacketConnection(conn, metadata)
207+
}
177208
return h.router.RoutePacketConnection(ctx, conn, metadata)
178209
}
179210

0 commit comments

Comments
 (0)