Compare commits

...

41 Commits

Author SHA1 Message Date
世界
bc35aca017 Bump version 2025-07-08 13:11:13 +08:00
世界
281d52a1ea Fix hy2 server crash 2025-07-08 13:11:13 +08:00
世界
b8502759b5 Fix DNS reject check 2025-07-07 13:57:37 +08:00
世界
6f804adf39 Fix v2rayhttp crash 2025-07-03 21:48:10 +08:00
Kyson
36db31c55a documentation: Fix typo
Co-authored-by: chenqixin <chenqixin@bytedance.com>
2025-06-29 18:54:05 +08:00
世界
4dbbf59c82 Fix logger for acme 2025-06-29 18:44:40 +08:00
世界
832eb4458d release: Fix xcode version 2025-06-29 18:44:40 +08:00
dyhkwong
2cf989d306 Fix inbound with v2ray transport missing InboundOptions 2025-06-25 13:20:00 +08:00
世界
7d3ee29bd0 Also skip duplicate sniff for TCP 2025-06-21 12:57:27 +08:00
世界
cba0e46aba Fix log for rejected connections 2025-06-21 12:57:26 +08:00
世界
9b8ab3e61e Bump version 2025-06-19 11:57:44 +08:00
dyhkwong
47f18e823a Fix: macOS udp find process should use unspecified fallback
be8d63ba8f
2025-06-18 08:34:59 +08:00
世界
2d1b824b62 Fix gLazyConn race 2025-06-17 14:24:11 +08:00
世界
d511698f3f Fix slowOpenConn 2025-06-12 08:05:04 +08:00
世界
cb435ea232 Fix default network strategy 2025-06-12 08:05:04 +08:00
世界
43a9016c83 Fix leak in hijack-dns 2025-06-06 14:28:09 +08:00
世界
255068fd40 Bump version 2025-06-04 23:32:10 +08:00
世界
098a00b025 Fix v2ray websocket transport 2025-06-04 23:23:36 +08:00
世界
dba0b5276b Bump version 2025-06-04 20:06:38 +08:00
Sentsuki
78ae935468 documentation: Fix typo
Signed-off-by: Sentsuki <52487960+Sentsuki@users.noreply.github.com>
2025-06-04 20:06:38 +08:00
Mahdi
3ea5f76470 Fix nil logger at v2rayhttp server 2025-06-04 20:06:20 +08:00
世界
b4d294c05e Fix TUIC read buffer 2025-06-04 20:03:51 +08:00
世界
83cf5f5c6a Fix ws closed error message 2025-05-27 14:30:07 +08:00
世界
e7b3a8eebe Fix vmess read request 2025-05-27 14:11:05 +08:00
世界
ee3a42a67e Fix none method read buffer 2025-05-27 14:03:48 +08:00
世界
50227c0f5f Fix sniff action 2025-05-26 18:24:35 +08:00
世界
bc5eb1e1a5 Fix RoutePacketConnectionEx 2025-05-24 08:14:43 +08:00
世界
995267a042 Remove wrong ALPNs in DOH/DOH3 2025-05-24 08:00:13 +08:00
世界
41226a6075 Fix interface finder 2025-05-23 10:57:38 +08:00
世界
81d32181ce Fix update route address set 2025-05-20 19:46:54 +08:00
世界
c5ecca3938 Bump version 2025-05-18 16:48:44 +08:00
世界
900888731c Fix DNS reject response 2025-05-13 18:05:31 +08:00
世界
13e648e4b1 Fix set edns0 subnet 2025-05-07 15:12:17 +08:00
世界
aff12ff671 Bump version 2025-05-05 09:37:47 +08:00
世界
101fb88255 Fix allocator put 2025-05-05 09:37:44 +08:00
世界
8b489354e4 Undeprecate the block outbound 2025-05-04 18:45:53 +08:00
世界
7dea6eb7a6 Fix missing read waiter for cancelers 2025-05-04 18:14:21 +08:00
世界
af1bfe4e3e Make rule_set.format optional 2025-05-04 18:14:21 +08:00
世界
d574e9eb52 Update smux to v1.5.34 2025-04-30 19:39:15 +08:00
世界
2d7df1e1f2 Fix hysteria bytes format 2025-04-29 20:45:19 +08:00
世界
1c0ffcf5b1 Fix counter position in auto redirect dnat rules 2025-04-28 11:20:23 +08:00
44 changed files with 434 additions and 435 deletions

View File

@@ -468,11 +468,11 @@ jobs:
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
sudo xcode-select -s /Applications/Xcode_16.4.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
sudo xcode-select -s /Applications/Xcode_16.4.app
- name: Set tag
if: matrix.if
run: |-

View File

@@ -1,6 +1,6 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme
TAGS_GO121 = with_ech
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server

View File

@@ -53,11 +53,11 @@ type InboundContext struct {
// sniffer
Protocol string
Domain string
Client string
SniffContext any
PacketSniffError error
Protocol string
Domain string
Client string
SniffContext any
SniffError error
// cache

View File

@@ -5,6 +5,7 @@ import (
"context"
"io"
"os"
"path/filepath"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
@@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
if err != nil {
return E.Cause(err, "read rule-set")
}
if flagRuleSetMatchFormat == "" {
switch filepath.Ext(sourcePath) {
case ".json":
flagRuleSetMatchFormat = C.RuleSetFormatSource
case ".srs":
flagRuleSetMatchFormat = C.RuleSetFormatBinary
}
}
var ruleSet option.PlainRuleSetCompat
switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource:

View File

@@ -100,10 +100,6 @@ 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 {
@@ -115,6 +111,10 @@ 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)

View File

@@ -10,9 +10,7 @@ import (
"sync"
"time"
"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"
@@ -26,7 +24,9 @@ type slowOpenConn struct {
destination M.Socksaddr
conn net.Conn
create chan struct{}
done chan struct{}
access sync.Mutex
closeOnce sync.Once
err error
}
@@ -45,6 +45,7 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
network: network,
destination: destination,
create: make(chan struct{}),
done: make(chan struct{}),
}, nil
}
@@ -55,8 +56,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
if c.err != nil {
return 0, c.err
}
case <-c.ctx.Done():
return 0, c.ctx.Err()
case <-c.done:
return 0, os.ErrClosed
}
}
return c.conn.Read(b)
@@ -74,12 +75,15 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
return 0, c.err
}
return c.conn.Write(b)
case <-c.done:
return 0, os.ErrClosed
default:
}
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
if err != nil {
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
c.err = err
} else {
c.conn = conn
}
n = len(b)
close(c.create)
@@ -87,7 +91,13 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
}
func (c *slowOpenConn) Close() error {
return common.Close(c.conn)
c.closeOnce.Do(func() {
close(c.done)
if c.conn != nil {
c.conn.Close()
}
})
return nil
}
func (c *slowOpenConn) LocalAddr() net.Addr {
@@ -152,8 +162,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
if c.err != nil {
return 0, c.err
}
case <-c.ctx.Done():
return 0, c.ctx.Err()
case <-c.done:
return 0, c.err
}
}
return bufio.Copy(w, c.conn)

View File

@@ -1,158 +0,0 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var defaultSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
var memorysSizeTable = map[string]uint64{
"b": Byte,
"kb": KiByte,
"mb": MiByte,
"gb": GiByte,
"tb": TiByte,
"pb": PiByte,
"eb": EiByte,
"": Byte,
"k": KiByte,
"m": MiByte,
"g": GiByte,
"t": TiByte,
"p": PiByte,
"e": EiByte,
}
var (
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
)
func Bytes(s uint64) string {
return humanateBytes(s, 1000, defaultSizes)
}
func MemoryBytes(s uint64) string {
return humanateBytes(s, 1024, defaultSizes)
}
func IBytes(s uint64) string {
return humanateBytes(s, 1024, iSizes)
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func ParseBytes(s string) (uint64, error) {
return parseBytes0(s, defaultSizeTable)
}
func ParseMemoryBytes(s string) (uint64, error) {
return parseBytes0(s, memorysSizeTable)
}
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := sizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

View File

@@ -76,6 +76,8 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// rup8(sizeof(xtcpcb_n))
itemSize += 208
}
var fallbackUDPProcess string
// skip the first xinpgen(24 bytes) block
for i := 24; i+itemSize <= len(buf); i += itemSize {
// offset of xinpcb_n and xsocket_n
@@ -90,10 +92,12 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
flag := buf[inp+44]
var srcIP netip.Addr
srcIsIPv4 := false
switch {
case flag&0x1 > 0 && isIPv4:
// ipv4
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
srcIsIPv4 = true
case flag&0x2 > 0 && !isIPv4:
// ipv6
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
@@ -101,13 +105,21 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
continue
}
if ip != srcIP {
continue
if ip == srcIP {
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
}
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
// udp packet connection may be not equal with srcIP
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
pid := readNativeUint32(buf[so+68 : so+72])
fallbackUDPProcess, _ = getExecPathFromPID(pid)
}
}
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
return fallbackUDPProcess, nil
}
return "", ErrNotFound

View File

@@ -5,13 +5,13 @@ package tls
import (
"context"
"crypto/tls"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/caddyserver/certmagic"
"github.com/libdns/alidns"
@@ -37,7 +37,38 @@ func (w *acmeWrapper) Close() error {
return nil
}
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
type acmeLogWriter struct {
logger logger.Logger
}
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
logLine := strings.ReplaceAll(string(p), " ", ": ")
switch {
case strings.HasPrefix(logLine, "error: "):
w.logger.Error(logLine[7:])
case strings.HasPrefix(logLine, "warn: "):
w.logger.Warn(logLine[6:])
case strings.HasPrefix(logLine, "info: "):
w.logger.Info(logLine[6:])
case strings.HasPrefix(logLine, "debug: "):
w.logger.Debug(logLine[7:])
default:
w.logger.Debug(logLine)
}
return len(p), nil
}
func (w *acmeLogWriter) Sync() error {
return nil
}
func encoderConfig() zapcore.EncoderConfig {
config := zap.NewProductionEncoderConfig()
config.TimeKey = zapcore.OmitKey
return config
}
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
var acmeServer string
switch options.Provider {
case "", "letsencrypt":
@@ -58,14 +89,15 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
} else {
storage = certmagic.Default.Storage
}
zapLogger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig()),
&acmeLogWriter{logger: logger},
zap.DebugLevel,
))
config := &certmagic.Config{
DefaultServerName: options.DefaultServerName,
Storage: storage,
Logger: zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
)),
Logger: zapLogger,
}
acmeConfig := certmagic.ACMEIssuer{
CA: acmeServer,
@@ -75,7 +107,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: config.Logger,
Logger: zapLogger,
}
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
var solver certmagic.DNS01Solver
@@ -103,6 +135,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
Logger: zapLogger,
})
config = certmagic.New(cache, *config)
var tlsConfig *tls.Config

View File

@@ -9,8 +9,9 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
}

View File

@@ -157,7 +157,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
//nolint:staticcheck
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
if err != nil {
return nil, err
}

View File

@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
conntrack.MemoryLimit = options.MemoryLimit.Value()
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller

View File

@@ -7,9 +7,9 @@ import (
"runtime/debug"
"strings"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/byteformats"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -2,11 +2,53 @@
icon: material/alert-decagram
---
### 1.11.15
* 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.11.14
* 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.11.13
* 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.11.11
* 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.11.10
* Undeprecate the `block` outbound **1**
* Fixes and improvements
**1**:
Since we dont have a replacement for using the `block` outbound in selectors yet,
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
_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.11.9
* 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)._
_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.11.8
@@ -18,25 +60,29 @@ _We are temporarily unable to update sing-box apps on the App Store because the
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
_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)._
_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.11.7
* 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)._
_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.11.6
* 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)._
_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.11.5
* 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)._
_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.11.4
@@ -46,7 +92,8 @@ _We are temporarily unable to update sing-box apps on the App Store because the
* Fixes and improvements
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
process._
### 1.11.1

View File

@@ -63,7 +63,7 @@ icon: material/new-box
{
"external_controller": "0.0.0.0:9090",
"external_ui": "dashboard"
// external_ui_download_detour: "direct"
// "external_ui_download_detour": "direct"
}
```

View File

@@ -63,7 +63,7 @@ icon: material/new-box
{
"external_controller": "0.0.0.0:9090",
"external_ui": "dashboard"
// external_ui_download_detour: "direct"
// "external_ui_download_detour": "direct"
}
```

View File

@@ -398,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` 栈。

View File

@@ -2,10 +2,6 @@
icon: material/delete-clock
---
!!! failure "Deprecated in sing-box 1.11.0"
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
### Structure
```json

View File

@@ -2,10 +2,6 @@
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.11.0 废弃"
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
`block` 出站关闭所有传入请求。
### 结构

View File

@@ -84,6 +84,8 @@ List of [Headless Rule](./headless-rule/).
Format of rule-set file, `source` or `binary`.
Optional when `path` or `url` uses `json` or `srs` as extension.
### Local Fields
#### path

View File

@@ -84,6 +84,8 @@ icon: material/new-box
规则集格式, `source` 或 `binary`。
当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。
### 本地字段
#### path

View File

@@ -7,10 +7,10 @@ import (
"strconv"
"time"
"github.com/sagernet/sing-box/common/humanize"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/locale"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/byteformats"
)
var (
@@ -75,11 +75,11 @@ func Version() string {
}
func FormatBytes(length int64) string {
return humanize.Bytes(uint64(length))
return byteformats.FormatBytes(uint64(length))
}
func FormatMemoryBytes(length int64) string {
return humanize.MemoryBytes(uint64(length))
return byteformats.FormatMemoryBytes(uint64(length))
}
func FormatDuration(duration int64) string {

18
go.mod
View File

@@ -26,16 +26,16 @@ require (
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.7
github.com/sagernet/sing-dns v0.4.2
github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing v0.6.10
github.com/sagernet/sing-dns v0.4.6
github.com/sagernet/sing-mux v0.3.2
github.com/sagernet/sing-quic v0.4.4
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.4
github.com/sagernet/sing-vmess v0.2.1
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/sing-tun v0.6.9
github.com/sagernet/sing-vmess v0.2.3
github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.7
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854

47
go.sum
View File

@@ -118,27 +118,27 @@ github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+
github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8WHNsRs71b3Lt1+p/U=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.7 h1:NIWBLZ9AUWDXAQBKGleKwsitbQrI9M0nqoheXhUKnrI=
github.com/sagernet/sing v0.6.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg=
github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk=
github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
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 v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4=
github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.6 h1:mjZC0o6d5sQ1sraoOBbK3G3apCbuL8wWYwu2RNu5rbM=
github.com/sagernet/sing-dns v0.4.6/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
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.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA=
github.com/sagernet/sing-quic v0.4.4/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
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-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
github.com/sagernet/sing-tun v0.6.4 h1:3Iew6UtAf1+mucVeHKNhAEQI5xmq3CUCbGptUbjebts=
github.com/sagernet/sing-tun v0.6.4/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug=
github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/sing-tun v0.6.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8=
github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44=
github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
@@ -150,8 +150,15 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
@@ -190,7 +197,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@@ -1,43 +1,14 @@
package option
import (
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing/common/json"
)
import "github.com/sagernet/sing/common/byteformats"
type DebugOptions struct {
Listen string `json:"listen,omitempty"`
GCPercent *int `json:"gc_percent,omitempty"`
MaxStack *int `json:"max_stack,omitempty"`
MaxThreads *int `json:"max_threads,omitempty"`
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
TraceBack string `json:"trace_back,omitempty"`
MemoryLimit MemoryBytes `json:"memory_limit,omitempty"`
OOMKiller *bool `json:"oom_killer,omitempty"`
}
type MemoryBytes uint64
func (l MemoryBytes) MarshalJSON() ([]byte, error) {
return json.Marshal(humanize.MemoryBytes(uint64(l)))
}
func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
var valueInteger int64
err := json.Unmarshal(bytes, &valueInteger)
if err == nil {
*l = MemoryBytes(valueInteger)
return nil
}
var valueString string
err = json.Unmarshal(bytes, &valueString)
if err != nil {
return err
}
parsedValue, err := humanize.ParseMemoryBytes(valueString)
if err != nil {
return err
}
*l = MemoryBytes(parsedValue)
return nil
Listen string `json:"listen,omitempty"`
GCPercent *int `json:"gc_percent,omitempty"`
MaxStack *int `json:"max_stack,omitempty"`
MaxThreads *int `json:"max_threads,omitempty"`
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
TraceBack string `json:"trace_back,omitempty"`
MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"`
OOMKiller *bool `json:"oom_killer,omitempty"`
}

View File

@@ -1,17 +1,19 @@
package option
import "github.com/sagernet/sing/common/byteformats"
type HysteriaInboundOptions struct {
ListenOptions
Up string `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down string `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Users []HysteriaUser `json:"users,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
MaxConnClient int `json:"max_conn_client,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Users []HysteriaUser `json:"users,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
MaxConnClient int `json:"max_conn_client,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
InboundTLSOptionsContainer
}
@@ -24,16 +26,16 @@ type HysteriaUser struct {
type HysteriaOutboundOptions struct {
DialerOptions
ServerOptions
Up string `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down string `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Auth []byte `json:"auth,omitempty"`
AuthString string `json:"auth_str,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindow uint64 `json:"recv_window,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Auth []byte `json:"auth,omitempty"`
AuthString string `json:"auth_str,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindow uint64 `json:"recv_window,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
}

View File

@@ -39,7 +39,7 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err
return E.New("missing outbound options registry in context")
}
switch h.Type {
case C.TypeBlock, C.TypeDNS:
case C.TypeDNS:
deprecated.Report(ctx, deprecated.OptionSpecialOutbounds)
}
options, loaded := registry.CreateOptions(h.Type)

View File

@@ -1,6 +1,8 @@
package option
import (
"net/url"
"path/filepath"
"reflect"
C "github.com/sagernet/sing-box/constant"
@@ -27,6 +29,18 @@ type _RuleSet struct {
type RuleSet _RuleSet
func (r RuleSet) MarshalJSON() ([]byte, error) {
if r.Type != C.RuleSetTypeInline {
var defaultFormat string
switch r.Type {
case C.RuleSetTypeLocal:
defaultFormat = ruleSetDefaultFormat(r.LocalOptions.Path)
case C.RuleSetTypeRemote:
defaultFormat = ruleSetDefaultFormat(r.RemoteOptions.URL)
}
if r.Format == defaultFormat {
r.Format = ""
}
}
var v any
switch r.Type {
case "", C.RuleSetTypeInline:
@@ -62,7 +76,19 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
default:
return E.New("unknown rule-set type: " + r.Type)
}
err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v)
if err != nil {
return err
}
if r.Type != C.RuleSetTypeInline {
if r.Format == "" {
switch r.Type {
case C.RuleSetTypeLocal:
r.Format = ruleSetDefaultFormat(r.LocalOptions.Path)
case C.RuleSetTypeRemote:
r.Format = ruleSetDefaultFormat(r.RemoteOptions.URL)
}
}
switch r.Format {
case "":
return E.New("missing format")
@@ -73,13 +99,23 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
} else {
r.Format = ""
}
err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v)
if err != nil {
return err
}
return nil
}
func ruleSetDefaultFormat(path string) string {
if pathURL, err := url.Parse(path); err == nil {
path = pathURL.Path
}
switch filepath.Ext(path) {
case ".json":
return C.RuleSetFormatSource
case ".srs":
return C.RuleSetFormatBinary
default:
return ""
}
}
type LocalRuleSet struct {
Path string `json:"path,omitempty"`
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
@@ -16,7 +15,6 @@ import (
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -56,19 +54,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
tlsConfig: tlsConfig,
}
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
if options.Up.Value() > 0 {
sendBps = options.Up.Value()
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if err != nil {
return nil, E.Cause(err, "invalid down speed format: ", options.Down)
}
if options.Down.Value() > 0 {
receiveBps = options.Down.Value()
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -59,19 +58,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
password = string(options.Auth)
}
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
if options.Up.Value() > 0 {
sendBps = options.Up.Value()
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if err != nil {
return nil, E.Cause(err, "invalid down speed format: ", options.Down)
}
if options.Down.Value() > 0 {
receiveBps = options.Down.Value()
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}

View File

@@ -214,7 +214,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if !loaded {
return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
}
ruleSet.IncRef()
inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
}
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
@@ -222,7 +221,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if !loaded {
return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
}
ruleSet.IncRef()
inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
}
if options.AutoRedirect {
@@ -312,7 +310,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
}
routeRuleSet.DecRef()
routeRuleSet.IncRef()
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
if t.autoRedirect != nil {
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
@@ -324,7 +322,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
}
routeExcludeRuleSet.DecRef()
routeExcludeRuleSet.IncRef()
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
if t.autoRedirect != nil {
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))

View File

@@ -205,6 +205,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
var metadata adapter.InboundContext
metadata.Source = source
metadata.Destination = destination
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -219,6 +219,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
var metadata adapter.InboundContext
metadata.Source = source
metadata.Destination = destination
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -2,15 +2,14 @@ package route
import (
"context"
"errors"
"net"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -31,7 +30,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) {
func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
if natConn, isNatConn := conn.(udpnat.Conn); isNatConn {
metadata.Destination = M.Socksaddr{}
for _, packet := range packetBuffers {
@@ -45,10 +44,12 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
conn: conn,
ctx: ctx,
metadata: metadata,
onClose: onClose,
})
return
}
err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata)
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil && !E.IsClosedOrCanceled(err) {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
}
@@ -56,7 +57,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) {
if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) {
router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
}
}
@@ -85,8 +86,16 @@ 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.conn, buffer, h.metadata, destination)
}
func (h *dnsHijacker) Close() error {
if h.onClose != nil {
h.onClose(nil)
}
return nil
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-mux"
"github.com/sagernet/sing-vmess"
@@ -51,7 +51,7 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata
err := r.routeConnection(ctx, conn, metadata, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
if E.IsClosedOrCanceled(err) {
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
r.logger.DebugContext(ctx, "connection closed: ", err)
} else {
r.logger.ErrorContext(ctx, err)
@@ -101,7 +101,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
var selectedOutbound adapter.Outbound
if selectedRule != nil {
switch action := selectedRule.Action().(type) {
case *rule.RuleActionRoute:
case *R.RuleActionRoute:
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
@@ -112,15 +112,15 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
buf.ReleaseMulti(buffers)
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
}
case *rule.RuleActionReject:
case *R.RuleActionReject:
buf.ReleaseMulti(buffers)
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
return nil
case *rule.RuleActionHijackDNS:
case *R.RuleActionHijackDNS:
for _, buffer := range buffers {
conn = bufio.NewCachedConn(conn, buffer)
}
r.hijackDNSStream(ctx, conn, metadata)
N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata))
return nil
}
}
@@ -154,7 +154,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
}))
if err != nil {
conn.Close()
if E.IsClosedOrCanceled(err) {
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
r.logger.DebugContext(ctx, "connection closed: ", err)
} else {
r.logger.ErrorContext(ctx, err)
@@ -171,13 +171,11 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
err := r.routePacketConnection(ctx, conn, metadata, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
if E.IsClosedOrCanceled(err) {
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
r.logger.DebugContext(ctx, "connection closed: ", err)
} else {
r.logger.ErrorContext(ctx, err)
}
} else if onClose != nil {
onClose(nil)
}
}
@@ -219,7 +217,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
var selectReturn bool
if selectedRule != nil {
switch action := selectedRule.Action().(type) {
case *rule.RuleActionRoute:
case *R.RuleActionRoute:
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
@@ -230,12 +228,12 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
N.ReleaseMultiPacketBuffer(packetBuffers)
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
}
case *rule.RuleActionReject:
case *R.RuleActionReject:
N.ReleaseMultiPacketBuffer(packetBuffers)
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
return nil
case *rule.RuleActionHijackDNS:
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata)
case *R.RuleActionHijackDNS:
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
return nil
}
}
@@ -273,7 +271,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error {
if selectedRule == nil {
return nil
}
rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject)
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject)
if !isReject {
return nil
}
@@ -348,7 +346,7 @@ func (r *Router) matchRule(
//nolint:staticcheck
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
if !preMatch && metadata.InboundOptions.SniffEnabled {
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
}, inputConn, inputPacketConn, nil)
@@ -363,7 +361,7 @@ func (r *Router) matchRule(
}
}
if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy),
})
if fatalErr != nil {
@@ -400,11 +398,11 @@ match:
}
}
}
var routeOptions *rule.RuleActionRouteOptions
var routeOptions *R.RuleActionRouteOptions
switch action := currentRule.Action().(type) {
case *rule.RuleActionRoute:
case *R.RuleActionRoute:
routeOptions = &action.RuleActionRouteOptions
case *rule.RuleActionRouteOptions:
case *R.RuleActionRouteOptions:
routeOptions = action
}
if routeOptions != nil {
@@ -450,7 +448,7 @@ match:
}
}
switch action := currentRule.Action().(type) {
case *rule.RuleActionSniff:
case *R.RuleActionSniff:
if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
if newErr != nil {
@@ -467,7 +465,7 @@ match:
selectedRuleIndex = currentRuleIndex
break match
}
case *rule.RuleActionResolve:
case *R.RuleActionResolve:
fatalErr = r.actionResolve(ctx, metadata, action)
if fatalErr != nil {
return
@@ -487,7 +485,7 @@ match:
}
func (r *Router) actionSniff(
ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
if sniff.Skip(metadata) {
@@ -498,7 +496,12 @@ func (r *Router) actionSniff(
return
}
if inputConn != nil {
sniffBuffer := buf.NewPacket()
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
return
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return
}
var streamSniffers []sniff.StreamSniffer
if len(action.StreamSniffers) > 0 {
streamSniffers = action.StreamSniffers
@@ -512,6 +515,7 @@ func (r *Router) actionSniff(
sniff.RDP,
}
}
sniffBuffer := buf.NewPacket()
err := sniff.PeekStream(
ctx,
metadata,
@@ -521,6 +525,7 @@ func (r *Router) actionSniff(
action.Timeout,
streamSniffers...,
)
metadata.SniffError = err
if err == nil {
//goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
@@ -543,9 +548,24 @@ func (r *Router) actionSniff(
sniffBuffer.Release()
}
} else if inputPacketConn != nil {
if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
return
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
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,
}
}
for {
var (
@@ -578,7 +598,7 @@ func (r *Router) actionSniff(
return
}
} else {
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
if len(packetBuffers) > 0 || metadata.SniffError != nil {
err = sniff.PeekPacket(
ctx,
metadata,
@@ -586,19 +606,6 @@ 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,
}
}
err = sniff.PeekPacket(
ctx, metadata,
sniffBuffer.Bytes(),
@@ -611,7 +618,7 @@ func (r *Router) actionSniff(
Destination: destination,
}
packetBuffers = append(packetBuffers, packetBuffer)
metadata.PacketSniffError = err
metadata.SniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
@@ -642,7 +649,7 @@ func (r *Router) actionSniff(
return
}
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error {
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error {
if metadata.Destination.IsFqdn() {
metadata.DNSServer = action.Server
addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy)

View File

@@ -161,9 +161,16 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
case *R.RuleActionReject:
switch action.Method {
case C.RuleActionRejectMethodDefault:
return dns.FixedResponse(message.Id, message.Question[0], nil, 0), nil
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeRefused,
Response: true,
},
Question: []mDNS.Question{message.Question[0]},
}, nil
case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop
return nil, &R.RejectedError{Cause: tun.ErrDrop}
}
}
}
@@ -282,7 +289,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
case C.RuleActionRejectMethodDefault:
return nil, nil
case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop
return nil, &R.RejectedError{Cause: tun.ErrDrop}
}
}
}

View File

@@ -2,6 +2,7 @@ package rule
import (
"context"
"errors"
"net/netip"
"strings"
"sync"
@@ -250,6 +251,23 @@ func (r *RuleActionDirect) String() string {
return "direct" + r.description
}
type RejectedError struct {
Cause error
}
func (r *RejectedError) Error() string {
return "rejected"
}
func (r *RejectedError) Unwrap() error {
return r.Cause
}
func IsRejected(err error) bool {
var rejected *RejectedError
return errors.As(err, &rejected)
}
type RuleActionReject struct {
Method string
NoDrop bool
@@ -273,9 +291,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
var returnErr error
switch r.Method {
case C.RuleActionRejectMethodDefault:
returnErr = syscall.ECONNREFUSED
returnErr = &RejectedError{syscall.ECONNREFUSED}
case C.RuleActionRejectMethodDrop:
return tun.ErrDrop
return &RejectedError{tun.ErrDrop}
default:
panic(F.ToString("unknown reject method: ", r.Method))
}
@@ -293,7 +311,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
if ctx != nil {
r.logger.DebugContext(ctx, "dropped due to flooding")
}
return tun.ErrDrop
return &RejectedError{tun.ErrDrop}
}
return returnErr
}

View File

@@ -31,6 +31,9 @@ type HTTPConn struct {
}
func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn {
if request.Header.Get("Host") == "" {
request.Header.Set("Host", request.Host)
}
return &HTTPConn{
Conn: conn,
request: request,
@@ -89,9 +92,6 @@ func (c *HTTPConn) writeRequest(payload []byte) error {
if err != nil {
return err
}
if c.request.Header.Get("Host") == "" {
c.request.Header.Set("Host", c.request.Host)
}
for key, value := range c.request.Header {
_, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF)))
if err != nil {

View File

@@ -47,6 +47,7 @@ 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),

View File

@@ -3,6 +3,7 @@ package v2raywebsocket
import (
"context"
"encoding/base64"
"errors"
"io"
"net"
"os"
@@ -67,9 +68,10 @@ 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 = c.reader.NextFrame()
header, err = wrapWsError0(c.reader.NextFrame())
if err != nil {
return
}
@@ -78,14 +80,14 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
err = wsutil.ErrFrameTooLarge
return
}
err = c.controlHandler(header, c.reader)
err = wrapWsError(c.controlHandler(header, c.reader))
if err != nil {
return
}
continue
}
if header.OpCode&ws.OpBinary == 0 {
err = c.reader.Discard()
err = wrapWsError(c.reader.Discard())
if err != nil {
return
}
@@ -95,7 +97,7 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
}
func (c *WebsocketConn) Write(p []byte) (n int, err error) {
err = wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p)
err = wrapWsError(wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p))
if err != nil {
return
}
@@ -146,7 +148,7 @@ func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
return 0, c.err
}
}
return c.conn.Read(b)
return wrapWsError0(c.conn.Read(b))
}
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
@@ -191,7 +193,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
if c.conn != nil {
return c.conn.Write(b)
return wrapWsError0(c.conn.Write(b))
}
c.access.Lock()
defer c.access.Unlock()
@@ -199,7 +201,7 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
return 0, c.err
}
if c.conn != nil {
return c.conn.Write(b)
return wrapWsError0(c.conn.Write(b))
}
err = c.writeRequest(b)
c.err = err
@@ -212,12 +214,12 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
if c.conn != nil {
return c.conn.WriteBuffer(buffer)
return wrapWsError(c.conn.WriteBuffer(buffer))
}
c.access.Lock()
defer c.access.Unlock()
if c.conn != nil {
return c.conn.WriteBuffer(buffer)
return wrapWsError(c.conn.WriteBuffer(buffer))
}
if c.err != nil {
return c.err
@@ -272,3 +274,23 @@ 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)
}

View File

@@ -1,22 +0,0 @@
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
}

View File

@@ -66,7 +66,7 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
ws.Cipher(data, *(*[4]byte)(header[1+payloadBitLength:]), 0)
}
return w.writer.WriteBuffer(buffer)
return wrapWsError(w.writer.WriteBuffer(buffer))
}
func (w *Writer) FrontHeadroom() int {