mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
9 Commits
renovate/m
...
v1.13.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3768cca36 | ||
|
|
0889ddd001 | ||
|
|
f46fbf188a | ||
|
|
f2d15139f5 | ||
|
|
041646b728 | ||
|
|
b990de2e12 | ||
|
|
fe585157d2 | ||
|
|
eed6a36e5d | ||
|
|
eb0f38544c |
Submodule clients/android updated: 7777469b5d...6f09892c71
Submodule clients/apple updated: c19945f65b...f3b4b2238e
@@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
@@ -229,7 +230,7 @@ func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) {
|
||||
record := c.rawConn.RawInput.Next(recordHeaderLen + n)
|
||||
data, typ, err = c.rawConn.In.Decrypt(record)
|
||||
if err != nil {
|
||||
err = c.rawConn.In.SetErrorLocked(c.sendAlert(uint8(err.(tls.AlertError))))
|
||||
err = c.rawConn.In.SetErrorLocked(c.sendAlert(*(*uint8)((*[2]unsafe.Pointer)(unsafe.Pointer(&err))[1])))
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -55,6 +55,12 @@ type contextKeyConnecting struct{}
|
||||
|
||||
var errRecursiveConnectorDial = E.New("recursive connector dial")
|
||||
|
||||
type connectorDialResult[T any] struct {
|
||||
connection T
|
||||
cancel context.CancelFunc
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *Connector[T]) Get(ctx context.Context) (T, error) {
|
||||
var zero T
|
||||
for {
|
||||
@@ -100,41 +106,37 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
c.connecting = make(chan struct{})
|
||||
connecting := make(chan struct{})
|
||||
c.connecting = connecting
|
||||
dialContext := context.WithValue(ctx, contextKeyConnecting{}, c)
|
||||
dialResult := make(chan connectorDialResult[T], 1)
|
||||
c.access.Unlock()
|
||||
|
||||
dialContext := context.WithValue(ctx, contextKeyConnecting{}, c)
|
||||
connection, cancel, err := c.dialWithCancellation(dialContext)
|
||||
go func() {
|
||||
connection, cancel, err := c.dialWithCancellation(dialContext)
|
||||
dialResult <- connectorDialResult[T]{
|
||||
connection: connection,
|
||||
cancel: cancel,
|
||||
err: err,
|
||||
}
|
||||
}()
|
||||
|
||||
c.access.Lock()
|
||||
close(c.connecting)
|
||||
c.connecting = nil
|
||||
|
||||
if err != nil {
|
||||
c.access.Unlock()
|
||||
return zero, err
|
||||
}
|
||||
|
||||
if c.closed {
|
||||
cancel()
|
||||
c.callbacks.Close(connection)
|
||||
c.access.Unlock()
|
||||
select {
|
||||
case result := <-dialResult:
|
||||
return c.completeDial(ctx, connecting, result)
|
||||
case <-ctx.Done():
|
||||
go func() {
|
||||
result := <-dialResult
|
||||
_, _ = c.completeDial(ctx, connecting, result)
|
||||
}()
|
||||
return zero, ctx.Err()
|
||||
case <-c.closeCtx.Done():
|
||||
go func() {
|
||||
result := <-dialResult
|
||||
_, _ = c.completeDial(ctx, connecting, result)
|
||||
}()
|
||||
return zero, ErrTransportClosed
|
||||
}
|
||||
if err = ctx.Err(); err != nil {
|
||||
cancel()
|
||||
c.callbacks.Close(connection)
|
||||
c.access.Unlock()
|
||||
return zero, err
|
||||
}
|
||||
|
||||
c.connection = connection
|
||||
c.hasConnection = true
|
||||
c.connectionCancel = cancel
|
||||
result := c.connection
|
||||
c.access.Unlock()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +145,38 @@ func isRecursiveConnectorDial[T any](ctx context.Context, connector *Connector[T
|
||||
return loaded && dialConnector == connector
|
||||
}
|
||||
|
||||
func (c *Connector[T]) completeDial(ctx context.Context, connecting chan struct{}, result connectorDialResult[T]) (T, error) {
|
||||
var zero T
|
||||
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
defer func() {
|
||||
if c.connecting == connecting {
|
||||
c.connecting = nil
|
||||
}
|
||||
close(connecting)
|
||||
}()
|
||||
|
||||
if result.err != nil {
|
||||
return zero, result.err
|
||||
}
|
||||
if c.closed || c.closeCtx.Err() != nil {
|
||||
result.cancel()
|
||||
c.callbacks.Close(result.connection)
|
||||
return zero, ErrTransportClosed
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
result.cancel()
|
||||
c.callbacks.Close(result.connection)
|
||||
return zero, err
|
||||
}
|
||||
|
||||
c.connection = result.connection
|
||||
c.hasConnection = true
|
||||
c.connectionCancel = result.cancel
|
||||
return c.connection, nil
|
||||
}
|
||||
|
||||
func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, context.CancelFunc, error) {
|
||||
var zero T
|
||||
if err := ctx.Err(); err != nil {
|
||||
|
||||
@@ -188,13 +188,157 @@ func TestConnectorCanceledRequestDoesNotCacheConnection(t *testing.T) {
|
||||
err := <-result
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
require.EqualValues(t, 1, dialCount.Load())
|
||||
require.EqualValues(t, 1, closeCount.Load())
|
||||
require.Eventually(t, func() bool {
|
||||
return closeCount.Load() == 1
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
_, err = connector.Get(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 2, dialCount.Load())
|
||||
}
|
||||
|
||||
func TestConnectorCanceledRequestReturnsBeforeIgnoredDialCompletes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
dialCount atomic.Int32
|
||||
closeCount atomic.Int32
|
||||
)
|
||||
dialStarted := make(chan struct{}, 1)
|
||||
releaseDial := make(chan struct{})
|
||||
|
||||
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||
dialCount.Add(1)
|
||||
select {
|
||||
case dialStarted <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
<-releaseDial
|
||||
return &testConnectorConnection{}, nil
|
||||
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||
IsClosed: func(connection *testConnectorConnection) bool {
|
||||
return false
|
||||
},
|
||||
Close: func(connection *testConnectorConnection) {
|
||||
closeCount.Add(1)
|
||||
},
|
||||
Reset: func(connection *testConnectorConnection) {},
|
||||
})
|
||||
|
||||
requestContext, cancel := context.WithCancel(context.Background())
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := connector.Get(requestContext)
|
||||
result <- err
|
||||
}()
|
||||
|
||||
<-dialStarted
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case err := <-result:
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Get did not return after request cancel")
|
||||
}
|
||||
|
||||
require.EqualValues(t, 1, dialCount.Load())
|
||||
require.EqualValues(t, 0, closeCount.Load())
|
||||
|
||||
close(releaseDial)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return closeCount.Load() == 1
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
_, err := connector.Get(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 2, dialCount.Load())
|
||||
}
|
||||
|
||||
func TestConnectorWaiterDoesNotStartNewDialBeforeCanceledDialCompletes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
dialCount atomic.Int32
|
||||
closeCount atomic.Int32
|
||||
)
|
||||
firstDialStarted := make(chan struct{}, 1)
|
||||
secondDialStarted := make(chan struct{}, 1)
|
||||
releaseFirstDial := make(chan struct{})
|
||||
|
||||
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||
attempt := dialCount.Add(1)
|
||||
switch attempt {
|
||||
case 1:
|
||||
select {
|
||||
case firstDialStarted <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
<-releaseFirstDial
|
||||
case 2:
|
||||
select {
|
||||
case secondDialStarted <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return &testConnectorConnection{}, nil
|
||||
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||
IsClosed: func(connection *testConnectorConnection) bool {
|
||||
return false
|
||||
},
|
||||
Close: func(connection *testConnectorConnection) {
|
||||
closeCount.Add(1)
|
||||
},
|
||||
Reset: func(connection *testConnectorConnection) {},
|
||||
})
|
||||
|
||||
requestContext, cancel := context.WithCancel(context.Background())
|
||||
firstResult := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := connector.Get(requestContext)
|
||||
firstResult <- err
|
||||
}()
|
||||
|
||||
<-firstDialStarted
|
||||
cancel()
|
||||
|
||||
secondResult := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := connector.Get(context.Background())
|
||||
secondResult <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-secondDialStarted:
|
||||
t.Fatal("second dial started before first dial completed")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-firstResult:
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("first Get did not return after request cancel")
|
||||
}
|
||||
|
||||
close(releaseFirstDial)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return closeCount.Load() == 1
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-secondDialStarted:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("second dial did not start after first dial completed")
|
||||
}
|
||||
|
||||
err := <-secondResult
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 2, dialCount.Load())
|
||||
}
|
||||
|
||||
func TestConnectorDialContextNotCanceledByRequestContextAfterDial(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -2,6 +2,30 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.13.3
|
||||
|
||||
* Add OpenWrt and Alpine APK packages to release **1**
|
||||
* Backport to macOS 10.13 High Sierra **2**
|
||||
* OCM service: Add WebSocket support for Responses API **3**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Alpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix:
|
||||
|
||||
- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk`
|
||||
- Alpine: `sing-box_{version}_linux_{architecture}.apk`
|
||||
|
||||
**2**:
|
||||
|
||||
Legacy macOS binaries (with `-legacy-macos-10.13` suffix) now support
|
||||
macOS 10.13 High Sierra, built using Go 1.25 with patches
|
||||
from [SagerNet/go](https://github.com/SagerNet/go).
|
||||
|
||||
**3**:
|
||||
|
||||
See [OCM](/configuration/service/ocm).
|
||||
|
||||
#### 1.13.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -38,7 +38,7 @@ icon: material/alert-decagram
|
||||
!!! warning "与官方 Hysteria2 的区别"
|
||||
|
||||
官方程序支持一种名为 **userpass** 的验证方式,
|
||||
本质上上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
|
||||
本质上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
|
||||
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
|
||||
|
||||
### 监听字段
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.3"
|
||||
|
||||
:material-alert: [strict_route](#strict_route)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
|
||||
@@ -348,6 +352,9 @@ Enforce strict routing rules when `auto_route` is enabled:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
|
||||
* When `auto_redirect` is enabled, `strict_route` also affects `SO_BINDTODEVICE` traffic:
|
||||
* Enabled: `SO_BINDTODEVICE` traffic is redirected through sing-box.
|
||||
* Disabled: `SO_BINDTODEVICE` traffic bypasses sing-box.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.13.3 中的更改"
|
||||
|
||||
:material-alert: [strict_route](#strict_route)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
|
||||
@@ -347,6 +351,9 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
* 使不支持的网络不可达。
|
||||
* 出于历史遗留原因,当未启用 `strict_route` 或 `auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
|
||||
* 当启用 `auto_redirect` 时,`strict_route` 也影响 `SO_BINDTODEVICE` 流量:
|
||||
* 启用:`SO_BINDTODEVICE` 流量被重定向通过 sing-box。
|
||||
* 禁用:`SO_BINDTODEVICE` 流量绕过 sing-box。
|
||||
|
||||
*在 Windows 中*:
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
!!! warning "与官方 Hysteria2 的区别"
|
||||
|
||||
官方程序支持一种名为 **userpass** 的验证方式,
|
||||
本质上上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
|
||||
本质上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
|
||||
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
|
||||
|
||||
### 字段
|
||||
|
||||
4
go.mod
4
go.mod
@@ -39,10 +39,10 @@ require (
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||
github.com/sagernet/sing-tun v0.8.2
|
||||
github.com/sagernet/sing-tun v0.8.3
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260310162543-0c2de366d4de
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260311131347-f88b27eeb76e
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.10.2
|
||||
|
||||
8
go.sum
8
go.sum
@@ -248,14 +248,14 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/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.8.2 h1:rQr/x3eQCHh3oleIaoJdPdJwqzZp4+QWcJLT0Wz2xKY=
|
||||
github.com/sagernet/sing-tun v0.8.2/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
github.com/sagernet/sing-tun v0.8.3 h1:mozxmuIoRhFdVHnheenLpBaammVj7bZPcnkApaYKDPY=
|
||||
github.com/sagernet/sing-tun v0.8.3/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260310162543-0c2de366d4de h1:wsJ0COxUOIvBE+hUho0C/DbMeUe9jtwfh6dECAiTk94=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260310162543-0c2de366d4de/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260311131347-f88b27eeb76e h1:Sv1qUhJIidjSTc24XEknovDZnbmVSlAXj8wNVgIfgGo=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260311131347-f88b27eeb76e/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg=
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
|
||||
@@ -333,9 +333,6 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
||||
t.systemTun = systemTun
|
||||
t.systemDialer = systemDialer
|
||||
t.server.TunDevice = wgTunDevice
|
||||
t.server.RouterWrapper = func(inner router.Router) router.Router {
|
||||
return &addressOnlyRouter{Router: inner}
|
||||
}
|
||||
}
|
||||
if mark := t.network.AutoRedirectOutputMark(); mark > 0 {
|
||||
controlFunc := t.network.AutoRedirectOutputMarkFunc()
|
||||
@@ -480,11 +477,12 @@ func (t *Endpoint) Close() error {
|
||||
t.fallbackTCPCloser()
|
||||
t.fallbackTCPCloser = nil
|
||||
}
|
||||
err := common.Close(common.PtrOrNil(t.server))
|
||||
if t.systemTun != nil {
|
||||
_ = t.systemTun.Close()
|
||||
t.systemTun.Close()
|
||||
t.systemTun = nil
|
||||
}
|
||||
return common.Close(common.PtrOrNil(t.server))
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
@@ -849,16 +847,3 @@ func (c *dnsConfigurtor) GetBaseConfig() (tsDNS.OSConfig, error) {
|
||||
func (c *dnsConfigurtor) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type addressOnlyRouter struct {
|
||||
router.Router
|
||||
}
|
||||
|
||||
func (r *addressOnlyRouter) Set(config *router.Config) error {
|
||||
if config != nil {
|
||||
config = &router.Config{
|
||||
LocalAddrs: config.LocalAddrs,
|
||||
}
|
||||
}
|
||||
return r.Router.Set(config)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user