mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-11 04:44:14 +08:00
Compare commits
95 Commits
dev-next
...
v1.12.0-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0613431623 | ||
![]() |
95aa5d05b7 | ||
![]() |
0ccfb8ad96 | ||
![]() |
86fe2aee98 | ||
![]() |
1b2cce77f1 | ||
![]() |
4b1a283bbf | ||
![]() |
f91cbc0e42 | ||
![]() |
7d1b88e1cd | ||
![]() |
a4ad775fb7 | ||
![]() |
88bfc6fd77 | ||
![]() |
cd8501736d | ||
![]() |
cc05b89b59 | ||
![]() |
f8bfe56458 | ||
![]() |
f9942b7c50 | ||
![]() |
41915a6621 | ||
![]() |
2d6894f28a | ||
![]() |
93ceb33780 | ||
![]() |
f643e35d00 | ||
![]() |
cbcc4f4cfb | ||
![]() |
feab546659 | ||
![]() |
5a3be48d7b | ||
![]() |
ef2f170364 | ||
![]() |
5022d9a924 | ||
![]() |
50d61c09cf | ||
![]() |
bcc703310e | ||
![]() |
a6c448e145 | ||
![]() |
11105eb553 | ||
![]() |
18e240e5b3 | ||
![]() |
03bb284370 | ||
![]() |
bc63aa2254 | ||
![]() |
abbe2f7eaf | ||
![]() |
33d576a018 | ||
![]() |
38e34c7719 | ||
![]() |
03d086c32e | ||
![]() |
52de97edf1 | ||
![]() |
0e68b1a29e | ||
![]() |
6b43758d8e | ||
![]() |
9957ab6e8b | ||
![]() |
36aef228bf | ||
![]() |
82ba228c39 | ||
![]() |
7acc036c6e | ||
![]() |
78439340cf | ||
![]() |
7fd99aa515 | ||
![]() |
5d30bfabae | ||
![]() |
c29d56699f | ||
![]() |
5c4518e6a4 | ||
![]() |
58bf4c1e85 | ||
![]() |
49d8408fdf | ||
![]() |
01cbc4810a | ||
![]() |
d9b5ca3e82 | ||
![]() |
6c2b4dce4a | ||
![]() |
daeb14bdd2 | ||
![]() |
7d7aa4a328 | ||
![]() |
f0f0b658f7 | ||
![]() |
de2546f572 | ||
![]() |
fcb8171ff5 | ||
![]() |
a53c6a906f | ||
![]() |
8637c536c8 | ||
![]() |
db7a0c6092 | ||
![]() |
87d287dd56 | ||
![]() |
ee65df4dfc | ||
![]() |
f9f5a0c724 | ||
![]() |
96e81b6e76 | ||
![]() |
883bea6628 | ||
![]() |
62b98fcc4c | ||
![]() |
c0d7198ac2 | ||
![]() |
da1cdcb784 | ||
![]() |
cd284a4368 | ||
![]() |
33e9d719f8 | ||
![]() |
f0a3ba5bc8 | ||
![]() |
b96a6b595e | ||
![]() |
be29171106 | ||
![]() |
c66b32c859 | ||
![]() |
984ebbb288 | ||
![]() |
c5929cf948 | ||
![]() |
be45dd2716 | ||
![]() |
a20f525048 | ||
![]() |
31ebe2f39f | ||
![]() |
61b9da2f1a | ||
![]() |
f5c738ea9d | ||
![]() |
ebc783b580 | ||
![]() |
a5b6dff812 | ||
![]() |
cc4e90c861 | ||
![]() |
2abce0959a | ||
![]() |
a98cb0fd84 | ||
![]() |
7b96406592 | ||
![]() |
4fea063b6f | ||
![]() |
1672d5a84a | ||
![]() |
4a1b69c1d4 | ||
![]() |
e2c1ff3a11 | ||
![]() |
37dd8fa692 | ||
![]() |
29b6a75f16 | ||
![]() |
7575746f72 | ||
![]() |
f35a58b7ee | ||
![]() |
4aacdade73 |
@ -8,7 +8,6 @@
|
||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||
--no-deb-generate-changes
|
||||
--config-files /etc/sing-box/config.json
|
||||
--after-install release/config/sing-box.postinst
|
||||
|
||||
release/config/config.json=/etc/sing-box/config.json
|
||||
|
||||
|
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@ -109,7 +109,7 @@ jobs:
|
||||
if: ${{ ! matrix.legacy_go }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Cache Legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
@ -294,7 +294,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -374,7 +374,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -472,7 +472,7 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
|
6
.github/workflows/linux.yml
vendored
6
.github/workflows/linux.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
- name: Set build tags
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api'
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: |
|
||||
|
2
box.go
2
box.go
@ -498,7 +498,7 @@ func (s *Box) Close() error {
|
||||
close(s.done)
|
||||
}
|
||||
err := common.Close(
|
||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||
)
|
||||
for _, lifecycleService := range s.internalService {
|
||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0
|
||||
Subproject commit cec05bf6935eca219a722883212ae8880d2e863e
|
@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
tag := tagVersion.VersionString()
|
||||
client := createClient(20 * time.Minute)
|
||||
client := createClient(10 * time.Minute)
|
||||
|
||||
log.Info(tag, " list build IDs")
|
||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||
@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
build := builds.Data[0]
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||
log.Info(string(platform), " ", tag, " waiting for process")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
|
@ -97,6 +97,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||
@ -108,10 +112,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@ -75,11 +76,10 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
return c.conn.Write(b)
|
||||
default:
|
||||
}
|
||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
} else {
|
||||
c.conn = conn
|
||||
c.conn = nil
|
||||
c.err = E.Cause(err, "dial tcp fast open")
|
||||
}
|
||||
n = len(b)
|
||||
close(c.create)
|
||||
|
@ -56,7 +56,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
|
||||
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
|
||||
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ type Client struct {
|
||||
disableCache bool
|
||||
disableExpire bool
|
||||
independentCache bool
|
||||
clientSubnet netip.Prefix
|
||||
rdrc adapter.RDRCStore
|
||||
initRDRCFunc func() adapter.RDRCStore
|
||||
logger logger.ContextLogger
|
||||
@ -48,7 +47,6 @@ type ClientOptions struct {
|
||||
DisableExpire bool
|
||||
IndependentCache bool
|
||||
CacheCapacity uint32
|
||||
ClientSubnet netip.Prefix
|
||||
RDRC func() adapter.RDRCStore
|
||||
Logger logger.ContextLogger
|
||||
}
|
||||
@ -59,7 +57,6 @@ func NewClient(options ClientOptions) *Client {
|
||||
disableCache: options.DisableCache,
|
||||
disableExpire: options.DisableExpire,
|
||||
independentCache: options.IndependentCache,
|
||||
clientSubnet: options.ClientSubnet,
|
||||
initRDRCFunc: options.RDRC,
|
||||
logger: options.Logger,
|
||||
}
|
||||
@ -107,12 +104,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
return &responseMessage, nil
|
||||
}
|
||||
question := message.Question[0]
|
||||
clientSubnet := options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
}
|
||||
if clientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, clientSubnet)
|
||||
if options.ClientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, options.ClientSubnet)
|
||||
}
|
||||
isSimpleRequest := len(message.Question) == 1 &&
|
||||
len(message.Ns) == 0 &&
|
||||
|
@ -55,7 +55,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||
RDRC: func() adapter.RDRCStore {
|
||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||
if cacheFile == nil {
|
||||
|
@ -2,40 +2,10 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-beta.23
|
||||
|
||||
* Add loopback address support for tun **1**
|
||||
* Add cache support for ssm-api **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
TUN now implements SideStore's StosVPN.
|
||||
|
||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
||||
|
||||
**2**:
|
||||
|
||||
See [SSM API Service](/configuration/service/ssm-api/#cache_path).
|
||||
|
||||
#### 1.12.0-beta.21
|
||||
|
||||
* Fix missing `home` option for DERP service **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
|
||||
|
||||
See [DERP](/configuration/service/derp/#home).
|
||||
|
||||
### 1.11.13
|
||||
#### 1.12.0-beta.18
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.17
|
||||
|
||||
* Update quic-go to v0.52.0
|
||||
|
@ -1,11 +1,7 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-decagram: [servers](#servers)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
@ -1,11 +1,7 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-decagram: [servers](#servers)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
@ -1,11 +1,7 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [loopback_address](#loopback_address)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
@ -60,12 +56,9 @@ icon: material/new-box
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
"auto_redirect": true,
|
||||
"auto_redirect": false,
|
||||
"auto_redirect_input_mark": "0x2023",
|
||||
"auto_redirect_output_mark": "0x2024",
|
||||
"loopback_address": [
|
||||
"10.0.7.1"
|
||||
],
|
||||
"strict_route": true,
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
@ -73,6 +66,7 @@ icon: material/new-box
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
@ -123,6 +117,7 @@ icon: material/new-box
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
|
||||
// Deprecated
|
||||
"gso": false,
|
||||
"inet4_address": [
|
||||
@ -145,8 +140,8 @@ icon: material/new-box
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
...
|
||||
// Listen Fields
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
@ -278,16 +273,6 @@ Connection output mark used by `auto_redirect`.
|
||||
|
||||
`0x2024` is used by default.
|
||||
|
||||
#### loopback_address
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Loopback addresses make TCP connections to the specified address connect to the source address.
|
||||
|
||||
Setting option value to `10.0.7.1` achieves the same behavior as SideStore/StosVPN.
|
||||
|
||||
When `auto_redirect` is enabled, the same behavior can be achieved for LAN devices (not just local) as a gateway.
|
||||
|
||||
#### strict_route
|
||||
|
||||
Enforce strict routing rules when `auto_route` is enabled:
|
||||
|
@ -1,11 +1,7 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [loopback_address](#loopback_address)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
@ -60,12 +56,9 @@ icon: material/new-box
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
"auto_redirect": true,
|
||||
"auto_redirect": false,
|
||||
"auto_redirect_input_mark": "0x2023",
|
||||
"auto_redirect_output_mark": "0x2024",
|
||||
"loopback_address": [
|
||||
"10.0.7.1"
|
||||
],
|
||||
"strict_route": true,
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
@ -277,16 +270,6 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
默认使用 `0x2024`。
|
||||
|
||||
#### loopback_address
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
|
||||
|
||||
将选项值设置为 `10.0.7.1` 可实现与 SideStore/StosVPN 相同的行为。
|
||||
|
||||
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。
|
||||
|
||||
#### strict_route
|
||||
|
||||
当启用 `auto_route` 时,强制执行严格的路由规则:
|
||||
@ -415,11 +398,11 @@ UDP NAT 过期时间。
|
||||
|
||||
TCP/IP 栈。
|
||||
|
||||
| 栈 | 描述 |
|
||||
|----------|-------------------------------------------------------------------------------------------------------|
|
||||
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
| 栈 | 描述 |
|
||||
|--------|------------------------------------------------------------------|
|
||||
| system | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
|
||||
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
||||
|
||||
|
@ -20,7 +20,6 @@ DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/
|
||||
"config_path": "",
|
||||
"verify_client_endpoint": [],
|
||||
"verify_client_url": [],
|
||||
"home": "",
|
||||
"mesh_with": [],
|
||||
"mesh_psk": "",
|
||||
"mesh_psk_file": "",
|
||||
@ -70,10 +69,6 @@ Setting Array value to a string `__URL__` is equivalent to configuring:
|
||||
{ "url": __URL__ }
|
||||
```
|
||||
|
||||
#### home
|
||||
|
||||
What to serve at the root path. It may be left empty (the default, for a default homepage), `blank` for a blank page, or a URL to redirect to
|
||||
|
||||
#### mesh_with
|
||||
|
||||
Mesh with other DERP servers.
|
||||
|
@ -10,7 +10,7 @@ icon: material/new-box
|
||||
|
||||
```json
|
||||
{
|
||||
"services": [
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
@ -25,7 +25,6 @@ icon: material/new-box
|
||||
|------------|------------------------|
|
||||
| `derp` | [DERP](./derp) |
|
||||
| `resolved` | [Resolved](./resolved) |
|
||||
| `ssm-api` | [SSM API](./ssm-api) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
@ -19,7 +19,6 @@ See https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadow
|
||||
... // Listen Fields
|
||||
|
||||
"servers": {},
|
||||
"cache_path": "",
|
||||
"tls": {}
|
||||
}
|
||||
```
|
||||
@ -38,7 +37,7 @@ A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inb
|
||||
|
||||
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -48,11 +47,6 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
#### cache_path
|
||||
|
||||
If set, when the server is about to stop, traffic and user state will be saved to the specified JSON file
|
||||
to be restored on the next startup.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
10
go.mod
10
go.mod
@ -30,12 +30,12 @@ require (
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
|
||||
github.com/sagernet/sing-mux v0.3.2
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210
|
||||
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8
|
||||
github.com/sagernet/smux v1.5.34-mod.2
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||
|
20
go.sum
20
go.sum
@ -172,18 +172,18 @@ github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf
|
||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.1 h1:nC0i/s8LhlZB8ev6laZCXF/uiwAE4kRdT4PcDdE4rI4=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.1/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5 h1:zlcioVa11g8VLz5L0yPG7PbvQrw7mrxkDDdlMPEgqDk=
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI=
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 h1:6H4BZaTqKI3YcDMyTV3E576LuJM4S4wY99xoq2T1ECw=
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8 h1:zW+zAOCxUIqBCgnZiPovt1uQ3S+zBS+w0NGp+1zITGA=
|
||||
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
type SSMAPIServiceOptions struct {
|
||||
ListenOptions
|
||||
Servers *badjson.TypedMap[string, string] `json:"servers"`
|
||||
CachePath string `json:"cache_path,omitempty"`
|
||||
Servers *badjson.TypedMap[string, string] `json:"servers"`
|
||||
InboundTLSOptionsContainer
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ type DERPServiceOptions struct {
|
||||
ConfigPath string `json:"config_path,omitempty"`
|
||||
VerifyClientEndpoint badoption.Listable[string] `json:"verify_client_endpoint,omitempty"`
|
||||
VerifyClientURL badoption.Listable[*DERPVerifyClientURLOptions] `json:"verify_client_url,omitempty"`
|
||||
Home string `json:"home,omitempty"`
|
||||
MeshWith badoption.Listable[*DERPMeshOptions] `json:"mesh_with,omitempty"`
|
||||
MeshPSK string `json:"mesh_psk,omitempty"`
|
||||
MeshPSKFile string `json:"mesh_psk_file,omitempty"`
|
||||
|
@ -20,7 +20,6 @@ type TunInboundOptions struct {
|
||||
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
||||
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
|
||||
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
|
||||
StrictRoute bool `json:"strict_route,omitempty"`
|
||||
RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
|
||||
RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"`
|
||||
|
@ -190,8 +190,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
IPRoute2RuleIndex: ruleIndex,
|
||||
AutoRedirectInputMark: inputMark,
|
||||
AutoRedirectOutputMark: outputMark,
|
||||
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
|
||||
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
|
||||
StrictRoute: options.StrictRoute,
|
||||
IncludeInterface: options.IncludeInterface,
|
||||
ExcludeInterface: options.ExcludeInterface,
|
||||
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
systemd-sysusers sing-box.conf
|
12
route/dns.go
12
route/dns.go
@ -36,7 +36,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||
func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext) error {
|
||||
if natConn, isNatConn := conn.(udpnat.Conn); isNatConn {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
for _, packet := range packetBuffers {
|
||||
@ -51,12 +51,10 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
|
||||
conn: conn,
|
||||
ctx: ctx,
|
||||
metadata: metadata,
|
||||
onClose: onClose,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
err := dnsOutbound.NewDNSPacketConnection(ctx, r.dns, conn, packetBuffers, metadata)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
if err != nil && !E.IsClosedOrCanceled(err) {
|
||||
return E.Cause(err, "process DNS packet")
|
||||
}
|
||||
@ -95,16 +93,8 @@ type dnsHijacker struct {
|
||||
conn N.PacketConn
|
||||
ctx context.Context
|
||||
metadata adapter.InboundContext
|
||||
onClose N.CloseHandlerFunc
|
||||
}
|
||||
|
||||
func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) {
|
||||
go ExchangeDNSPacket(h.ctx, h.router, h.logger, h.conn, buffer, h.metadata, destination)
|
||||
}
|
||||
|
||||
func (h *dnsHijacker) Close() error {
|
||||
if h.onClose != nil {
|
||||
h.onClose(nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -117,8 +117,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
for _, buffer := range buffers {
|
||||
conn = bufio.NewCachedConn(conn, buffer)
|
||||
}
|
||||
N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata))
|
||||
return nil
|
||||
return r.hijackDNSStream(ctx, conn, metadata)
|
||||
}
|
||||
}
|
||||
if selectedRule == nil {
|
||||
@ -229,7 +228,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||
return action.Error(ctx)
|
||||
case *rule.RuleActionHijackDNS:
|
||||
return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
|
||||
return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata)
|
||||
|
||||
}
|
||||
}
|
||||
if selectedRule == nil || selectReturn {
|
||||
@ -499,9 +499,7 @@ func (r *Router) actionSniff(
|
||||
return
|
||||
}
|
||||
if inputConn != nil {
|
||||
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
||||
return
|
||||
}
|
||||
sniffBuffer := buf.NewPacket()
|
||||
var streamSniffers []sniff.StreamSniffer
|
||||
if len(action.StreamSniffers) > 0 {
|
||||
streamSniffers = action.StreamSniffers
|
||||
@ -515,7 +513,6 @@ func (r *Router) actionSniff(
|
||||
sniff.RDP,
|
||||
}
|
||||
}
|
||||
sniffBuffer := buf.NewPacket()
|
||||
err := sniff.PeekStream(
|
||||
ctx,
|
||||
metadata,
|
||||
@ -547,26 +544,10 @@ func (r *Router) actionSniff(
|
||||
sniffBuffer.Release()
|
||||
}
|
||||
} else if inputPacketConn != nil {
|
||||
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
||||
return
|
||||
} else if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
|
||||
if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
|
||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
|
||||
return
|
||||
}
|
||||
var packetSniffers []sniff.PacketSniffer
|
||||
if len(action.PacketSniffers) > 0 {
|
||||
packetSniffers = action.PacketSniffers
|
||||
} else {
|
||||
packetSniffers = []sniff.PacketSniffer{
|
||||
sniff.DomainNameQuery,
|
||||
sniff.QUICClientHello,
|
||||
sniff.STUNMessage,
|
||||
sniff.UTP,
|
||||
sniff.UDPTracker,
|
||||
sniff.DTLSRecord,
|
||||
sniff.NTP,
|
||||
}
|
||||
}
|
||||
for {
|
||||
var (
|
||||
sniffBuffer = buf.NewPacket()
|
||||
@ -606,6 +587,20 @@ func (r *Router) actionSniff(
|
||||
sniff.QUICClientHello,
|
||||
)
|
||||
} else {
|
||||
var packetSniffers []sniff.PacketSniffer
|
||||
if len(action.PacketSniffers) > 0 {
|
||||
packetSniffers = action.PacketSniffers
|
||||
} else {
|
||||
packetSniffers = []sniff.PacketSniffer{
|
||||
sniff.DomainNameQuery,
|
||||
sniff.QUICClientHello,
|
||||
sniff.STUNMessage,
|
||||
sniff.UTP,
|
||||
sniff.UDPTracker,
|
||||
sniff.DTLSRecord,
|
||||
sniff.NTP,
|
||||
}
|
||||
}
|
||||
err = sniff.PeekPacket(
|
||||
ctx, metadata,
|
||||
sniffBuffer.Bytes(),
|
||||
|
@ -124,7 +124,6 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
||||
configPath: configPath,
|
||||
verifyClientEndpoint: options.VerifyClientEndpoint,
|
||||
verifyClientURL: options.VerifyClientURL,
|
||||
home: options.Home,
|
||||
meshKey: options.MeshPSK,
|
||||
meshKeyPath: options.MeshPSKFile,
|
||||
meshWith: options.MeshWith,
|
||||
@ -134,7 +133,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
||||
func (d *Service) Start(stage adapter.StartStage) error {
|
||||
switch stage {
|
||||
case adapter.StartStateStart:
|
||||
config, err := readDERPConfig(filemanager.BasePath(d.ctx, d.configPath))
|
||||
config, err := readDERPConfig(d.configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,222 +0,0 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
Endpoints *badjson.TypedMap[string, *EndpointCache] `json:"endpoints"`
|
||||
}
|
||||
|
||||
type EndpointCache struct {
|
||||
GlobalUplink int64 `json:"global_uplink"`
|
||||
GlobalDownlink int64 `json:"global_downlink"`
|
||||
GlobalUplinkPackets int64 `json:"global_uplink_packets"`
|
||||
GlobalDownlinkPackets int64 `json:"global_downlink_packets"`
|
||||
GlobalTCPSessions int64 `json:"global_tcp_sessions"`
|
||||
GlobalUDPSessions int64 `json:"global_udp_sessions"`
|
||||
UserUplink *badjson.TypedMap[string, int64] `json:"user_uplink"`
|
||||
UserDownlink *badjson.TypedMap[string, int64] `json:"user_downlink"`
|
||||
UserUplinkPackets *badjson.TypedMap[string, int64] `json:"user_uplink_packets"`
|
||||
UserDownlinkPackets *badjson.TypedMap[string, int64] `json:"user_downlink_packets"`
|
||||
UserTCPSessions *badjson.TypedMap[string, int64] `json:"user_tcp_sessions"`
|
||||
UserUDPSessions *badjson.TypedMap[string, int64] `json:"user_udp_sessions"`
|
||||
Users *badjson.TypedMap[string, string] `json:"users"`
|
||||
}
|
||||
|
||||
func (s *Service) loadCache() error {
|
||||
if s.cachePath == "" {
|
||||
return nil
|
||||
}
|
||||
basePath := filemanager.BasePath(s.ctx, s.cachePath)
|
||||
cacheBinary, err := os.ReadFile(basePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = s.decodeCache(cacheBinary)
|
||||
if err != nil {
|
||||
os.RemoveAll(basePath)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) saveCache() error {
|
||||
if s.cachePath == "" {
|
||||
return nil
|
||||
}
|
||||
basePath := filemanager.BasePath(s.ctx, s.cachePath)
|
||||
err := os.MkdirAll(filepath.Dir(basePath), 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheBinary, err := s.encodeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(s.cachePath, cacheBinary, 0o644)
|
||||
}
|
||||
|
||||
func (s *Service) decodeCache(cacheBinary []byte) error {
|
||||
if len(cacheBinary) == 0 {
|
||||
return nil
|
||||
}
|
||||
cache, err := json.UnmarshalExtended[*Cache](cacheBinary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cache.Endpoints == nil || cache.Endpoints.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, entry := range cache.Endpoints.Entries() {
|
||||
trafficManager, loaded := s.traffics[entry.Key]
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
trafficManager.globalUplink.Store(entry.Value.GlobalUplink)
|
||||
trafficManager.globalDownlink.Store(entry.Value.GlobalDownlink)
|
||||
trafficManager.globalUplinkPackets.Store(entry.Value.GlobalUplinkPackets)
|
||||
trafficManager.globalDownlinkPackets.Store(entry.Value.GlobalDownlinkPackets)
|
||||
trafficManager.globalTCPSessions.Store(entry.Value.GlobalTCPSessions)
|
||||
trafficManager.globalUDPSessions.Store(entry.Value.GlobalUDPSessions)
|
||||
trafficManager.userUplink = typedAtomicInt64Map(entry.Value.UserUplink)
|
||||
trafficManager.userDownlink = typedAtomicInt64Map(entry.Value.UserDownlink)
|
||||
trafficManager.userUplinkPackets = typedAtomicInt64Map(entry.Value.UserUplinkPackets)
|
||||
trafficManager.userDownlinkPackets = typedAtomicInt64Map(entry.Value.UserDownlinkPackets)
|
||||
trafficManager.userTCPSessions = typedAtomicInt64Map(entry.Value.UserTCPSessions)
|
||||
trafficManager.userUDPSessions = typedAtomicInt64Map(entry.Value.UserUDPSessions)
|
||||
userManager, loaded := s.users[entry.Key]
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
userManager.usersMap = typedMap(entry.Value.Users)
|
||||
_ = userManager.postUpdate(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) encodeCache() ([]byte, error) {
|
||||
endpoints := new(badjson.TypedMap[string, *EndpointCache])
|
||||
for tag, traffic := range s.traffics {
|
||||
var (
|
||||
userUplink = new(badjson.TypedMap[string, int64])
|
||||
userDownlink = new(badjson.TypedMap[string, int64])
|
||||
userUplinkPackets = new(badjson.TypedMap[string, int64])
|
||||
userDownlinkPackets = new(badjson.TypedMap[string, int64])
|
||||
userTCPSessions = new(badjson.TypedMap[string, int64])
|
||||
userUDPSessions = new(badjson.TypedMap[string, int64])
|
||||
userMap = new(badjson.TypedMap[string, string])
|
||||
)
|
||||
for user, uplink := range traffic.userUplink {
|
||||
if uplink.Load() > 0 {
|
||||
userUplink.Put(user, uplink.Load())
|
||||
}
|
||||
}
|
||||
for user, downlink := range traffic.userDownlink {
|
||||
if downlink.Load() > 0 {
|
||||
userDownlink.Put(user, downlink.Load())
|
||||
}
|
||||
}
|
||||
for user, uplinkPackets := range traffic.userUplinkPackets {
|
||||
if uplinkPackets.Load() > 0 {
|
||||
userUplinkPackets.Put(user, uplinkPackets.Load())
|
||||
}
|
||||
}
|
||||
for user, downlinkPackets := range traffic.userDownlinkPackets {
|
||||
if downlinkPackets.Load() > 0 {
|
||||
userDownlinkPackets.Put(user, downlinkPackets.Load())
|
||||
}
|
||||
}
|
||||
for user, tcpSessions := range traffic.userTCPSessions {
|
||||
if tcpSessions.Load() > 0 {
|
||||
userTCPSessions.Put(user, tcpSessions.Load())
|
||||
}
|
||||
}
|
||||
for user, udpSessions := range traffic.userUDPSessions {
|
||||
if udpSessions.Load() > 0 {
|
||||
userUDPSessions.Put(user, udpSessions.Load())
|
||||
}
|
||||
}
|
||||
userManager := s.users[tag]
|
||||
if userManager != nil && len(userManager.usersMap) > 0 {
|
||||
userMap = new(badjson.TypedMap[string, string])
|
||||
for username, password := range userManager.usersMap {
|
||||
if username != "" && password != "" {
|
||||
userMap.Put(username, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
endpoints.Put(tag, &EndpointCache{
|
||||
GlobalUplink: traffic.globalUplink.Load(),
|
||||
GlobalDownlink: traffic.globalDownlink.Load(),
|
||||
GlobalUplinkPackets: traffic.globalUplinkPackets.Load(),
|
||||
GlobalDownlinkPackets: traffic.globalDownlinkPackets.Load(),
|
||||
GlobalTCPSessions: traffic.globalTCPSessions.Load(),
|
||||
GlobalUDPSessions: traffic.globalUDPSessions.Load(),
|
||||
UserUplink: sortTypedMap(userUplink),
|
||||
UserDownlink: sortTypedMap(userDownlink),
|
||||
UserUplinkPackets: sortTypedMap(userUplinkPackets),
|
||||
UserDownlinkPackets: sortTypedMap(userDownlinkPackets),
|
||||
UserTCPSessions: sortTypedMap(userTCPSessions),
|
||||
UserUDPSessions: sortTypedMap(userUDPSessions),
|
||||
Users: sortTypedMap(userMap),
|
||||
})
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
encoder := json.NewEncoder(&buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err := encoder.Encode(&Cache{
|
||||
Endpoints: sortTypedMap(endpoints),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func sortTypedMap[T comparable](trafficMap *badjson.TypedMap[string, T]) *badjson.TypedMap[string, T] {
|
||||
if trafficMap == nil {
|
||||
return nil
|
||||
}
|
||||
keys := trafficMap.Keys()
|
||||
sort.Strings(keys)
|
||||
sortedMap := new(badjson.TypedMap[string, T])
|
||||
for _, key := range keys {
|
||||
value, _ := trafficMap.Get(key)
|
||||
sortedMap.Put(key, value)
|
||||
}
|
||||
return sortedMap
|
||||
}
|
||||
|
||||
func typedAtomicInt64Map(trafficMap *badjson.TypedMap[string, int64]) map[string]*atomic.Int64 {
|
||||
result := make(map[string]*atomic.Int64)
|
||||
if trafficMap != nil {
|
||||
for _, entry := range trafficMap.Entries() {
|
||||
counter := new(atomic.Int64)
|
||||
counter.Store(entry.Value)
|
||||
result[entry.Key] = counter
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func typedMap[T comparable](trafficMap *badjson.TypedMap[string, T]) map[string]T {
|
||||
result := make(map[string]T)
|
||||
if trafficMap != nil {
|
||||
for _, entry := range trafficMap.Entries() {
|
||||
result[entry.Key] = entry.Value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -33,9 +33,6 @@ type Service struct {
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
httpServer *http.Server
|
||||
traffics map[string]*TrafficManager
|
||||
users map[string]*UserManager
|
||||
cachePath string
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.SSMAPIServiceOptions) (adapter.Service, error) {
|
||||
@ -53,9 +50,6 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
||||
httpServer: &http.Server{
|
||||
Handler: chiRouter,
|
||||
},
|
||||
traffics: make(map[string]*TrafficManager),
|
||||
users: make(map[string]*UserManager),
|
||||
cachePath: options.CachePath,
|
||||
}
|
||||
inboundManager := service.FromContext[adapter.InboundManager](ctx)
|
||||
if options.Servers.Size() == 0 {
|
||||
@ -74,8 +68,6 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
||||
managedServer.SetTracker(traffic)
|
||||
user := NewUserManager(managedServer, traffic)
|
||||
chiRouter.Route(entry.Key, NewAPIServer(logger, traffic, user).Route)
|
||||
s.traffics[entry.Key] = traffic
|
||||
s.users[entry.Key] = user
|
||||
}
|
||||
if options.TLS != nil {
|
||||
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||
@ -91,12 +83,8 @@ func (s *Service) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
err := s.loadCache()
|
||||
if err != nil {
|
||||
s.logger.Error(E.Cause(err, "load cache"))
|
||||
}
|
||||
if s.tlsConfig != nil {
|
||||
err = s.tlsConfig.Start()
|
||||
err := s.tlsConfig.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "create TLS config")
|
||||
}
|
||||
@ -121,10 +109,6 @@ func (s *Service) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (s *Service) Close() error {
|
||||
err := s.saveCache()
|
||||
if err != nil {
|
||||
s.logger.Error(E.Cause(err, "save cache"))
|
||||
}
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.httpServer),
|
||||
common.PtrOrNil(s.listener),
|
||||
|
@ -22,7 +22,7 @@ func NewUserManager(inbound adapter.ManagedSSMServer, trafficManager *TrafficMan
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserManager) postUpdate(updated bool) error {
|
||||
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 {
|
||||
@ -33,9 +33,7 @@ func (m *UserManager) postUpdate(updated bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if updated {
|
||||
m.trafficManager.UpdateUsers(users)
|
||||
}
|
||||
m.trafficManager.UpdateUsers(users)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -60,7 +58,7 @@ func (m *UserManager) Add(username string, password string) error {
|
||||
return E.New("user ", username, " already exists")
|
||||
}
|
||||
m.usersMap[username] = password
|
||||
return m.postUpdate(true)
|
||||
return m.postUpdate()
|
||||
}
|
||||
|
||||
func (m *UserManager) Get(username string) (string, bool) {
|
||||
@ -76,12 +74,12 @@ func (m *UserManager) Update(username string, password string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.usersMap[username] = password
|
||||
return m.postUpdate(true)
|
||||
return m.postUpdate()
|
||||
}
|
||||
|
||||
func (m *UserManager) Delete(username string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
delete(m.usersMap, username)
|
||||
return m.postUpdate(true)
|
||||
return m.postUpdate()
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option.
|
||||
server := &Server{
|
||||
ctx: ctx,
|
||||
tlsConfig: tlsConfig,
|
||||
logger: logger,
|
||||
handler: handler,
|
||||
h2Server: &http2.Server{
|
||||
IdleTimeout: time.Duration(options.IdleTimeout),
|
||||
|
@ -3,7 +3,6 @@ package v2raywebsocket
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
@ -68,10 +67,9 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
if !E.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) {
|
||||
err = wrapWsError(err)
|
||||
return
|
||||
}
|
||||
header, err = wrapWsError0(c.reader.NextFrame())
|
||||
header, err = c.reader.NextFrame()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -80,14 +78,14 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
err = wsutil.ErrFrameTooLarge
|
||||
return
|
||||
}
|
||||
err = wrapWsError(c.controlHandler(header, c.reader))
|
||||
err = c.controlHandler(header, c.reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if header.OpCode&ws.OpBinary == 0 {
|
||||
err = wrapWsError(c.reader.Discard())
|
||||
err = c.reader.Discard()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -97,7 +95,7 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) Write(p []byte) (n int, err error) {
|
||||
err = wrapWsError(wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p))
|
||||
err = wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -148,7 +146,7 @@ func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
|
||||
return 0, c.err
|
||||
}
|
||||
}
|
||||
return wrapWsError0(c.conn.Read(b))
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||
@ -193,7 +191,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||
|
||||
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||
if c.conn != nil {
|
||||
return wrapWsError0(c.conn.Write(b))
|
||||
return c.conn.Write(b)
|
||||
}
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
@ -201,7 +199,7 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||
return 0, c.err
|
||||
}
|
||||
if c.conn != nil {
|
||||
return wrapWsError0(c.conn.Write(b))
|
||||
return c.conn.Write(b)
|
||||
}
|
||||
err = c.writeRequest(b)
|
||||
c.err = err
|
||||
@ -214,12 +212,12 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||
|
||||
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if c.conn != nil {
|
||||
return wrapWsError(c.conn.WriteBuffer(buffer))
|
||||
return c.conn.WriteBuffer(buffer)
|
||||
}
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
if c.conn != nil {
|
||||
return wrapWsError(c.conn.WriteBuffer(buffer))
|
||||
return c.conn.WriteBuffer(buffer)
|
||||
}
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
@ -274,23 +272,3 @@ func (c *EarlyWebsocketConn) Upstream() any {
|
||||
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
||||
return c.conn == nil
|
||||
}
|
||||
|
||||
func wrapWsError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var closedErr wsutil.ClosedError
|
||||
if errors.As(err, &closedErr) {
|
||||
if closedErr.Code == ws.StatusNormalClosure {
|
||||
err = io.EOF
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func wrapWsError0[T any](value T, err error) (T, error) {
|
||||
if err == nil {
|
||||
return value, nil
|
||||
}
|
||||
return value, wrapWsError(err)
|
||||
}
|
||||
|
22
transport/v2raywebsocket/deadline.go
Normal file
22
transport/v2raywebsocket/deadline.go
Normal file
@ -0,0 +1,22 @@
|
||||
package v2raywebsocket
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type deadConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *deadConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *deadConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *deadConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
@ -66,7 +66,7 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
|
||||
ws.Cipher(data, *(*[4]byte)(header[1+payloadBitLength:]), 0)
|
||||
}
|
||||
|
||||
return wrapWsError(w.writer.WriteBuffer(buffer))
|
||||
return w.writer.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (w *Writer) FrontHeadroom() int {
|
||||
|
Loading…
x
Reference in New Issue
Block a user