Compare commits

...

31 Commits

Author SHA1 Message Date
世界
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
世界
348cc39975 Bump version 2025-04-27 21:33:31 +08:00
世界
987899f94a Fix usages of wireguard listener 2025-04-27 21:29:23 +08:00
世界
d8b2d5142f Fix panic on some stupid input 2025-04-25 16:03:58 +08:00
世界
134802d1ee Fix ssh outbound 2025-04-25 16:03:57 +08:00
世界
e5e81b4de1 Fix wireguard listening 2025-04-25 16:03:57 +08:00
世界
300c961efa option: Fix listable again and again 2025-04-25 16:03:57 +08:00
世界
7c7f512405 option: Fix omitempty reject method 2025-04-25 16:03:57 +08:00
世界
03e8d029c2 release: Fix apt-get install 2025-04-25 16:03:57 +08:00
世界
787b5f1931 Fix set wireguard reserved on Linux 2025-04-25 16:03:57 +08:00
世界
56a7624618 Fix vmess working with zero uuids 2025-04-25 16:03:57 +08:00
世界
3a84acf122 Fix hysteria1 server panic 2025-04-25 16:03:57 +08:00
世界
f600e02e47 Fix DNS crash 2025-04-25 16:03:57 +08:00
世界
e6d19de58a Fix overriding address 2025-04-22 14:55:44 +08:00
dyhkwong
f2bbf6b2aa Fix sniffer errors override each others
* Fix sniffer errors override each others

* Do not return ErrNeedMoreData if header is not expected
2025-04-22 14:44:55 +08:00
dyhkwong
c54d50fd36 Fix websocket detour
Signed-off-by: trimgop <20010323+trimgop@users.noreply.github.com>
Co-authored-by: trimgop <20010323+trimgop@users.noreply.github.com>
2025-04-22 14:44:34 +08:00
世界
6a051054db release: Fix packages 2025-04-19 19:12:01 +08:00
43 changed files with 500 additions and 447 deletions

View File

@@ -181,13 +181,14 @@ jobs:
fi
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}-1"
PKG_VERSION="${PKG_VERSION//-/\~}"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
@@ -226,6 +227,7 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y libarchive-tools
cp .fpm_systemd .fpm
fpm -t pacman \

View File

@@ -120,6 +120,7 @@ jobs:
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
--name "${NAME}" \
-v "$PKG_VERSION" \
@@ -138,6 +139,7 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
--name "${NAME}" \
-v "$PKG_VERSION" \

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

@@ -333,7 +333,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return d.udpListener.ListenPacket(context.Background(), network, address)
udpListener := d.udpListener
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
for _, wgControlFn := range WgControlFns {
err := wgControlFn(network, address, conn)
if err != nil {
return err
}
}
return nil
})
return udpListener.ListenPacket(context.Background(), network, address)
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {

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

@@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
return os.ErrInvalid
}
const header = "BitTorrent protocol"
var protocol [19]byte
_, err = reader.Read(protocol[:])
var n int
n, err = reader.Read(protocol[:])
if string(protocol[:n]) != header[:n] {
return os.ErrInvalid
}
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
if string(protocol[:]) != "BitTorrent protocol" {
return os.ErrInvalid
if n < 19 {
return ErrNeedMoreData
}
metadata.Protocol = C.ProtocolBitTorrent

View File

@@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
}
}
func TestSniffIncompleteBittorrent(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("13426974546f7272656e74")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotBittorrent(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("13426974546f7272656e75")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffUTP(t *testing.T) {
t.Parallel()

View File

@@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
if length == 0 {
if length < 12 {
return os.ErrInvalid
}
buffer := buf.NewSize(int(length))
defer buffer.Release()
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
var n int
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
packet := buffer.Bytes()
if n > 2 && packet[2]&0x80 != 0 { // QR
return os.ErrInvalid
}
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
return os.ErrInvalid
}
for i := 6; i < 10; i++ {
// ANCOUNT, NSCOUNT
if n > i && packet[i] != 0 {
return os.ErrInvalid
}
}
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
return DomainNameQuery(readCtx, metadata, packet)
}
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
var msg mDNS.Msg
err := msg.Unpack(packet)
if err != nil {
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
return err
}
metadata.Protocol = C.ProtocolDNS

View File

@@ -1,6 +1,7 @@
package sniff_test
import (
"bytes"
"context"
"encoding/hex"
"testing"
@@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) {
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}
func TestSniffStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}
func TestSniffIncompleteStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000001000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000000000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}

View File

@@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
sniffError = E.Errors(sniffError, err)
}
if !errors.Is(err, ErrNeedMoreData) {
if !errors.Is(sniffError, ErrNeedMoreData) {
break
}
}

View File

@@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
const sshPrefix = "SSH-2.0-"
bReader := bufio.NewReader(reader)
prefix, err := bReader.Peek(len(sshPrefix))
if string(prefix[:]) != sshPrefix[:len(prefix)] {
return os.ErrInvalid
}
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
} else if string(prefix) != sshPrefix {
return os.ErrInvalid
}
fistLine, _, err := bReader.ReadLine()
if err != nil {

View File

@@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
require.Equal(t, "dropbear", metadata.Client)
}
func TestSniffIncompleteSSH(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("5353482d322e30")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotSSH(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("5353482d322e31")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}

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,6 +2,33 @@
icon: material/alert-decagram
---
### 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)._
### 1.11.8
* Improve `auto_redirect` **1**
@@ -12,25 +39,29 @@ icon: material/alert-decagram
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
@@ -40,7 +71,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

@@ -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

@@ -8,56 +8,57 @@ icon: material/package
=== ":material-debian: Debian / APT"
```bash
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta
```
```bash
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 5"
```bash
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
```bash
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 4"
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
## :material-download-box: Manual Installation
The script download and install the latest package from GitHub releases for deb or rpm based Linux distributions, ArchLinux and OpenWrt.
The script download and install the latest package from GitHub releases
for deb or rpm based Linux distributions, ArchLinux and OpenWrt.
```shell
curl -fsSL https://sing-box.app/install.sh | sh
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh
```
or latest beta:
or latest beta:
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
```
or specific version:
or specific version:
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
```
## :material-book-lock-open: Managed Installation

View File

@@ -26,39 +26,38 @@ icon: material/package
=== ":material-redhat: Redhat / DNF 5"
```bash
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
```bash
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 4"
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
## :material-download-box: 手动安装
=== ":material-debian: Debian / DEB"
该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。
```bash
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh
```
=== ":material-redhat: Redhat / RPM"
或最新测试版:
```bash
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
```
(这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。)
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
```
=== ":simple-archlinux: Archlinux / PKG"
或指定版本:
```bash
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
```
## :material-book-lock-open: 托管安装

View File

@@ -3,76 +3,92 @@
download_beta=false
download_version=""
for arg in "$@"; do
if [[ "$arg" == "--beta" ]]; then
download_beta=true
elif [[ "$arg" == "--version" ]]; then
download_version=true
elif [[ "$download_version" == 'true' ]]; then
download_version="$arg"
else
echo "Unknown argument: $arg"
echo "Usage: $0 [--beta] [--version <version>]"
exit 1
fi
while [ $# -gt 0 ]; do
case "$1" in
--beta)
download_beta=true
shift
;;
--version)
shift
if [ $# -eq 0 ]; then
echo "Missing argument for --version"
echo "Usage: $0 [--beta] [--version <version>]"
exit 1
fi
download_version="$1"
shift
;;
*)
echo "Unknown argument: $1"
echo "Usage: $0 [--beta] [--version <version>]"
exit 1
;;
esac
done
if [[ $(command -v dpkg) ]]; then
os="linux"
arch=$(dpkg --print-architecture)
package_suffix=".deb"
package_install="dpkg -i"
elif [[ $(command -v dnf) ]]; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="dnf install -y"
elif [[ $(command -v rpm) ]]; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="rpm -i"
elif [[ $(command -v pacman) ]]; then
if command -v pacman >/dev/null 2>&1; then
os="linux"
arch=$(uname -m)
package_suffix=".pkg.tar.zst"
package_install="pacman -U --noconfirm"
elif [[ $(command -v opkg) ]]; then
elif command -v dpkg >/dev/null 2>&1; then
os="linux"
arch=$(dpkg --print-architecture)
package_suffix=".deb"
package_install="dpkg -i"
elif command -v dnf >/dev/null 2>&1; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="dnf install -y"
elif command -v rpm >/dev/null 2>&1; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="rpm -i"
elif command -v opkg >/dev/null 2>&1; then
os="openwrt"
source /etc/os-release
. /etc/os-release
arch="$OPENWRT_ARCH"
package_suffix=".ipk"
package_install="opkg update && opkg install -y"
package_install="opkg update && opkg install"
else
echo "Missing supported package manager."
exit 1
fi
if [[ -z "$download_version" ]]; then
if [[ "$download_beta" != 'true' ]]; then
if [[ -n "$GITHUB_TOKEN" ]]; then
latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest)
if [ -z "$download_version" ]; then
if [ "$download_beta" != "true" ]; then
if [ -n "$GITHUB_TOKEN" ]; then
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest)
else
latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases/latest)
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest)
fi
curl_exit_status=$?
if [[ $curl_exit_status -ne 0 ]]; then
echo "$latest_release"
exit $?
if [ $curl_exit_status -ne 0 ]; then
exit $curl_exit_status
fi
download_version=$(echo "$latest_release" | grep tag_name | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//')
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
echo "$latest_release"
exit 1
fi
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
else
if [[ -n "$GITHUB_TOKEN" ]]; then
latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases)
if [ -n "$GITHUB_TOKEN" ]; then
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases)
else
latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases)
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases)
fi
curl_exit_status=$?
if [[ $? -ne 0 ]]; then
echo "$latest_release"
exit $?
if [ $curl_exit_status -ne 0 ]; then
exit $curl_exit_status
fi
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//')
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
echo "$latest_release"
exit 1
fi
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
fi
fi
@@ -80,18 +96,21 @@ package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}"
package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}"
echo "Downloading $package_url"
if [[ -n "$GITHUB_TOKEN" ]]; then
curl --fail-with-body -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url"
if [ -n "$GITHUB_TOKEN" ]; then
curl --fail -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url"
else
curl --fail-with-body -Lo "$package_name" "$package_url"
curl --fail -Lo "$package_name" "$package_url"
fi
if [[ $? -ne 0 ]]; then
exit $?
curl_exit_status=$?
if [ $curl_exit_status -ne 0 ]; then
exit $curl_exit_status
fi
if [[ $(command -v sudo) ]]; then
if command -v sudo >/dev/null 2>&1; then
package_install="sudo $package_install"
fi
echo "$package_install $package_name" && $package_install "$package_name" && rm "$package_name"
echo "$package_install $package_name"
sh -c "$package_install \"$package_name\""
rm -f "$package_name"

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 {

16
go.mod
View File

@@ -26,18 +26,18 @@ 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.6
github.com/sagernet/sing-dns v0.4.1
github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1
github.com/sagernet/sing v0.6.10
github.com/sagernet/sing-dns v0.4.5
github.com/sagernet/sing-mux v0.3.2
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-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.4
github.com/sagernet/sing-vmess v0.2.0
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/sing-tun v0.6.5
github.com/sagernet/sing-vmess v0.2.1
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.5
github.com/sagernet/wireguard-go v0.0.1-beta.7
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0

45
go.sum
View File

@@ -118,33 +118,31 @@ 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.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU=
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE=
github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk=
github.com/sagernet/sing-dns v0.4.1/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 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4=
github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
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.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5hZI=
github.com/sagernet/sing-dns v0.4.5/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.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-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.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
github.com/sagernet/sing-vmess v0.2.0/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.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg=
github.com/sagernet/sing-tun v0.6.5/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 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.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
@@ -152,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=
@@ -192,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

@@ -252,6 +252,14 @@ type _RejectActionOptions struct {
type RejectActionOptions _RejectActionOptions
func (r RejectActionOptions) MarshalJSON() ([]byte, error) {
switch r.Method {
case C.RuleActionRejectMethodDefault:
r.Method = ""
}
return json.Marshal((_RejectActionOptions)(r))
}
func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_RejectActionOptions)(r))
if err != nil {

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

@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
@@ -191,9 +192,29 @@ func (s *Outbound) DialContext(ctx context.Context, network string, destination
if err != nil {
return nil, err
}
return client.Dial(network, destination.String())
conn, err := client.Dial(network, destination.String())
if err != nil {
return nil, err
}
return &chanConnWrapper{Conn: conn}, nil
}
func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}
type chanConnWrapper struct {
net.Conn
}
func (c *chanConnWrapper) SetDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *chanConnWrapper) SetReadDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *chanConnWrapper) SetWriteDeadline(t time.Time) error {
return os.ErrInvalid
}

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

@@ -176,8 +176,6 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
} else {
r.logger.ErrorContext(ctx, err)
}
} else if onClose != nil {
onClose(nil)
}
}
@@ -418,6 +416,7 @@ match:
Port: metadata.Destination.Port,
Fqdn: routeOptions.OverrideAddress.Fqdn,
}
metadata.DestinationAddresses = nil
}
if routeOptions.OverridePort > 0 {
metadata.Destination = M.Socksaddr{

View File

@@ -161,7 +161,14 @@ 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
}
@@ -223,6 +230,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
err error
)
printResult := func() {
if err == nil && len(responseAddrs) == 0 {
err = E.New("empty result")
}
if err != nil {
if errors.Is(err, dns.ErrResponseRejectedCached) {
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
@@ -231,9 +241,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
} else {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
}
} else if len(responseAddrs) == 0 {
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
err = dns.RCodeNameError
}
}
responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy)

View File

@@ -102,7 +102,10 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
rule.allItems = append(rule.allItems, item)
}
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
item := NewDomainItem(options.Domain, options.DomainSuffix)
item, err := NewDomainItem(options.Domain, options.DomainSuffix)
if err != nil {
return nil, err
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}

View File

@@ -93,7 +93,10 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.allItems = append(rule.allItems, item)
}
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
item := NewDomainItem(options.Domain, options.DomainSuffix)
item, err := NewDomainItem(options.Domain, options.DomainSuffix)
if err != nil {
return nil, err
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}

View File

@@ -47,7 +47,10 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
rule.allItems = append(rule.allItems, item)
}
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
item := NewDomainItem(options.Domain, options.DomainSuffix)
item, err := NewDomainItem(options.Domain, options.DomainSuffix)
if err != nil {
return nil, err
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
} else if options.DomainMatcher != nil {

View File

@@ -5,6 +5,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/domain"
E "github.com/sagernet/sing/common/exceptions"
)
var _ RuleItem = (*DomainItem)(nil)
@@ -14,7 +15,17 @@ type DomainItem struct {
description string
}
func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem {
func NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) {
for _, domainItem := range domains {
if domainItem == "" {
return nil, E.New("domain: empty item is not allowed")
}
}
for _, domainSuffixItem := range domainSuffixes {
if domainSuffixItem == "" {
return nil, E.New("domain_suffix: empty item is not allowed")
}
}
var description string
if dLen := len(domains); dLen > 0 {
if dLen == 1 {
@@ -40,7 +51,7 @@ func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem {
return &DomainItem{
domain.NewMatcher(domains, domainSuffixes, false),
description,
}
}, nil
}
func NewRawDomainItem(matcher *domain.Matcher) *DomainItem {

View File

@@ -91,10 +91,7 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h
} else {
deadlineConn = conn
}
err = deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout))
if err != nil {
return nil, E.Cause(err, "set read deadline")
}
deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout))
var protocols []string
if protocolHeader := headers.Get("Sec-WebSocket-Protocol"); protocolHeader != "" {
protocols = []string{protocolHeader}

View File

@@ -141,7 +141,7 @@ func (e *Endpoint) Start(resolve bool) error {
return nil
}
var bind conn.Bind
wgListener, isWgListener := e.options.Dialer.(conn.Listener)
wgListener, isWgListener := common.Cast[conn.Listener](e.options.Dialer)
if isWgListener {
bind = conn.NewStdNetBind(wgListener)
} else {