Compare commits

..

138 Commits

Author SHA1 Message Date
世界
a530e424e9 Bump version 2025-03-27 18:17:39 +08:00
世界
0bfd487ee9 Fix udpnat2 handler again 2025-03-27 18:17:39 +08:00
世界
6aae834493 release: Fix workflow 2025-03-27 18:17:39 +08:00
世界
f56131f38e Make linter happy 2025-03-24 20:38:42 +08:00
世界
273a11d550 Fix crash on udpnat2 handler 2025-03-24 18:14:32 +08:00
世界
ae8ce75e41 Fix websocket crash 2025-03-24 17:44:14 +08:00
世界
d6d94b689f release: Replace goreleaser build with scripts 2025-03-24 13:48:37 +08:00
世界
30d785f1ee release: Use fake goreleaser key 2025-03-21 22:25:51 +08:00
世界
db5ec3cdfc Fix connectionCopyEarly 2025-03-21 10:51:16 +08:00
世界
9aca54d039 Fix socks5 UDP 2025-03-16 14:46:44 +08:00
世界
d55d5009c2 Fix processing multiple sniffs 2025-03-16 09:21:54 +08:00
世界
4f3ee61104 Fix copy early conn 2025-03-15 08:09:04 +08:00
世界
96eb98c00a Fix httpupgrade crash 2025-03-14 17:17:28 +08:00
世界
68ce9577c6 Fix context in v2ray http transports 2025-03-14 17:07:17 +08:00
世界
3ae036e997 Downgrade goreleaser to stable since nfpm fixed 2025-03-13 18:53:19 +08:00
世界
5da2d1d470 release: Fix goreleaser version 2025-03-12 16:15:50 +08:00
世界
8e2baf40f1 Bump version 2025-03-11 20:18:34 +08:00
世界
c24c40dfee platform: Fix android start 2025-03-11 20:18:34 +08:00
世界
32e52ce1ed Fix udp nat for fakeip 2025-03-11 19:09:27 +08:00
世界
ed46438359 release: Use nightly goreleaser to fix rpm bug 2025-03-11 13:29:08 +08:00
世界
0b5490d5a3 Fix resolve domain for WireGuard 2025-03-11 12:02:25 +08:00
Tal Rasha
2d73ef511d Fix grpclite memory leak
Co-authored-by: talrasha007 <talrasha007@gmail.om>
2025-03-10 14:48:02 +08:00
Mahdi
63e6c85f6f Fix shadowsocks UoT 2025-03-10 14:47:59 +08:00
世界
8946a6d2d0 release: Use latest goreleaser 2025-03-09 15:27:04 +08:00
世界
d3132645fb documentation: Fix description of the UoT protocol 2025-03-09 15:26:42 +08:00
世界
373f158fe0 Fix download external ui with query params 2025-03-09 15:26:36 +08:00
世界
ce36835fab Fix override destination 2025-03-09 15:25:06 +08:00
世界
619fa671d7 Skip binding to the default interface as it will fail on some Android devices 2025-02-26 07:25:35 +08:00
世界
eb07c7a79e Bump version 2025-02-24 07:27:55 +08:00
Gavin Luo
7eb3535094 release: Fix systemd permissions 2025-02-24 07:27:55 +08:00
世界
93b68312cf platform: Add update WIFI state func 2025-02-23 08:35:30 +08:00
世界
97ce666e43 Fix http.FileServer short write 2025-02-23 08:35:30 +08:00
世界
4000e1e66d release: Fix update android version 2025-02-23 08:35:30 +08:00
世界
270740e859 Fix crash on route address set update 2025-02-23 08:35:30 +08:00
世界
6cad142cfe Bump Go to go1.24 2025-02-23 08:35:30 +08:00
世界
093013687c Fix sniff QUIC hidden in three or more packets 2025-02-18 18:14:59 +08:00
世界
ff31c469a0 Override version 2025-02-11 15:55:15 +08:00
世界
fbe390268c Bump version 2025-02-11 01:32:14 +08:00
世界
07ac01dcb7 platform: Update NDK to r28 2025-02-11 01:32:14 +08:00
ReleTor
badfdb62cd documentation: Fixes 2025-02-11 01:32:14 +08:00
printfer
986a410b30 documentation: Fix migration links 2025-02-11 01:32:14 +08:00
世界
9db2d58545 Fix override address 2025-02-11 01:32:14 +08:00
世界
4eed46ac59 Fix respond ICMP echo 2025-02-10 15:12:10 +08:00
世界
abc38d1dab Fix udpnat2 crash 2025-02-10 15:11:26 +08:00
世界
8d6c4f1289 release: Skip testflight when another build in review 2025-02-06 12:02:47 +08:00
世界
a2d40eb8b8 Fix override UDP destination 2025-02-06 11:20:35 +08:00
世界
17b502bb4b Update dependencies 2025-02-06 09:08:52 +08:00
世界
a0d4421085 Update quic-go to v0.49.0 2025-02-06 08:50:21 +08:00
世界
0d443072d1 Fix panic in auto-redirect initialize 2025-02-06 08:49:25 +08:00
世界
c9fb99b799 Fix missing ENOTCONN in IsClosed check 2025-02-06 08:48:49 +08:00
世界
92d245ad04 Bump version 2025-02-05 09:59:52 +08:00
世界
0908627297 Fix crash on remote rule-set stop 2025-02-05 08:58:10 +08:00
世界
7f79458b4f Minor updates 2025-02-01 19:49:33 +08:00
世界
9b4c11ba95 Fix rule-set not closed 2025-02-01 19:49:33 +08:00
世界
27c31eac5d Fix local rule-set not updated 2025-02-01 19:42:21 +08:00
世界
bab8dc0b82 Fix missing handshake for early conn 2025-01-31 12:57:35 +08:00
世界
d09d2fb665 Bump version 2025-01-30 15:09:54 +08:00
世界
e64cf3b7df Do not set address sets to routes on Apple platforms
Network Extension was observed to stop for unknown reasons
2025-01-27 13:40:39 +08:00
世界
9b73222314 documentation: Bump version 2025-01-27 10:53:14 +08:00
世界
3923b57abf release: Update NDK to r28-beta3 2025-01-27 10:53:14 +08:00
世界
4807e64609 documentation: Fix typo 2025-01-27 10:04:07 +08:00
世界
eeb37d89f1 Fix rule-set upgrade command 2025-01-27 09:50:27 +08:00
世界
08c1ec4b7e Fix legacy routes 2025-01-27 09:50:27 +08:00
HystericalDragon
6b4cf67add Fix endpoints not close 2025-01-27 09:50:27 +08:00
世界
e65926fd08 Fix tests 2025-01-13 15:14:30 +08:00
世界
f2ec319fe1 Fix system time 2025-01-13 15:14:30 +08:00
世界
32377a61b7 Add port hopping for hysteria2 2025-01-13 15:14:30 +08:00
世界
7aac801ccd tun: Set address sets to routes 2025-01-13 15:14:30 +08:00
世界
96fdf59ee4 Fix default dialer on legacy xiaomi systems 2025-01-13 15:14:30 +08:00
世界
50b8f3ab94 Add rule-set merge command 2025-01-13 15:14:30 +08:00
世界
ff7aaf977b Fix DNS match 2025-01-13 15:14:30 +08:00
世界
9a1efbe54d Fix domain strategy 2025-01-13 15:14:30 +08:00
世界
906c21f458 Fix time service 2025-01-13 15:14:30 +08:00
世界
d5e7af7a7e Fix socks5 UDP implementation 2025-01-13 15:14:30 +08:00
世界
4d41f03bd5 clash-api: Fix missing endpoints 2025-01-13 15:14:30 +08:00
世界
30704a15a7 hysteria2: Add more masquerade options 2025-01-13 15:14:30 +08:00
世界
83889178ed Improve timeouts 2025-01-13 15:14:30 +08:00
世界
1d2720bf5e Add UDP timeout route option 2025-01-13 15:14:30 +08:00
世界
c4b6d0eadb Make GSO adaptive 2025-01-13 15:14:30 +08:00
世界
0c66888691 Fix lint 2025-01-13 15:14:30 +08:00
世界
68781387fe refactor: WireGuard endpoint 2025-01-13 15:14:30 +08:00
世界
fd299a0961 refactor: connection manager 2025-01-13 15:14:30 +08:00
世界
285a82050c documentation: Fix typo 2025-01-13 15:14:30 +08:00
世界
2dbb8c55c9 Add override destination to route options 2025-01-13 15:14:30 +08:00
世界
effcf39469 Add dns.cache_capacity 2025-01-13 15:14:30 +08:00
世界
9db9484863 Refactor multi networks strategy 2025-01-13 15:14:30 +08:00
世界
ca813f461b documentation: Remove unused titles 2025-01-13 15:14:30 +08:00
世界
bb46cdb2b3 Add multi network dialing 2025-01-13 15:14:30 +08:00
世界
dcb10c21a1 documentation: Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
05ea0ca00e Add network_[type/is_expensive/is_constrained] rule items 2025-01-13 15:14:29 +08:00
世界
c098f282b1 Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
ecf82d197c refactor: Platform Interfaces 2025-01-13 15:14:29 +08:00
世界
9afe75586a refactor: Extract services form router 2025-01-13 15:14:29 +08:00
世界
a1be455202 refactor: Modular network manager 2025-01-13 15:14:29 +08:00
世界
19fb214226 refactor: Modular inbound/outbound manager 2025-01-13 15:14:29 +08:00
世界
28ec898a8c documentation: Add rule action 2025-01-13 15:14:29 +08:00
世界
467b1bbeeb documentation: Update the scheduled removal time of deprecated features 2025-01-13 15:14:29 +08:00
世界
02ab8ce806 documentation: Remove outdated icons 2025-01-13 15:14:29 +08:00
世界
ce69e620e9 Migrate bad options to library 2025-01-13 15:14:29 +08:00
世界
1133cf3ef5 Implement udp connect 2025-01-13 15:14:29 +08:00
世界
59a607e303 Implement new deprecated warnings 2025-01-13 15:14:29 +08:00
世界
313be3d7a4 Improve rule actions 2025-01-13 15:14:29 +08:00
世界
4fe40fcee0 Remove unused reject methods 2025-01-13 15:14:29 +08:00
世界
e233fd4fe5 refactor: Modular inbounds/outbounds 2025-01-13 15:14:29 +08:00
世界
9f7683818f Implement dns-hijack 2025-01-13 15:14:29 +08:00
世界
179e3cb2f5 Implement resolve(server) 2025-01-13 15:14:29 +08:00
世界
41b960552d Implement TCP and ICMP rejects 2025-01-13 15:14:29 +08:00
世界
8304295c48 Crazy sekai overturns the small pond 2025-01-13 15:14:29 +08:00
世界
253b41936e Bump version 2025-01-11 20:58:00 +08:00
世界
ce5b4b06b5 auto-redirect: Fix fetch interfaces 2025-01-07 21:24:52 +08:00
世界
50f5006c43 Fix leak in reality server 2025-01-07 17:27:10 +08:00
世界
e42ff22c2e quic: Fix source not unwrapped 2025-01-07 17:27:10 +08:00
世界
578571b972 Bump version 2025-01-02 15:21:13 +08:00
世界
935beca45d Fix check interface 2025-01-01 12:19:56 +08:00
世界
3e246f1173 Fix vmess mux leak 2025-01-01 12:18:56 +08:00
世界
1bc27a32c2 Fix linter configuration 2025-01-01 12:18:56 +08:00
世界
bc2e3960e4 Enable fix stack for Android 7 and 9 2024-12-28 15:06:57 +08:00
世界
9c4ab0bf33 Bump version 2024-12-21 17:20:56 +08:00
世界
27bdef34c7 release: Fix create app store version 2024-12-21 17:20:56 +08:00
世界
3c00099ed4 Fix process rule check 2024-12-21 17:15:41 +08:00
世界
2babf07f9a Fix wireguard 2024-12-21 16:07:19 +08:00
世界
4795ed712b release: Fix android version 2024-12-21 16:07:19 +08:00
世界
d4cd564dbe release: Fix check tag 2024-12-21 16:07:19 +08:00
世界
1676e13d3e Bump version 2024-12-17 13:43:17 +08:00
世界
50576084c6 release: Add publish testflight 2024-12-17 13:43:17 +08:00
世界
3a94e792a2 documentation: Fix uid range example 2024-12-15 13:56:25 +08:00
世界
9f69f41f68 Update http file server usage 2024-12-15 13:56:25 +08:00
世界
e6847ff50e Add locale support ford deprecated messages 2024-12-15 02:57:08 +08:00
世界
2ac2589d14 release: Fix publish testflight 2024-12-15 02:57:08 +08:00
世界
64a94e8144 release: Fix UpdateBuildForAppStoreVersion 2024-12-14 20:18:36 +08:00
世界
3ed8a5c5d1 wireguard: Fix windows tun read 2024-12-14 20:18:36 +08:00
世界
0a922c6fe3 release: Fix Xcode version 2024-12-14 20:18:36 +08:00
世界
52f3a4226c release: Add app store connect actions 2024-12-14 20:18:36 +08:00
世界
483d9fa503 release: Fix check prerelease 2024-12-14 20:18:36 +08:00
世界
dd9de694f8 release: Update macOS project version atomically 2024-12-14 20:18:36 +08:00
世界
5cdf5c1d9e release: Fix play release 2024-12-14 20:18:36 +08:00
世界
cec7e47086 Add workaround for bulkBarrierPreWrite: unaligned arguments panic 2024-12-14 20:18:36 +08:00
世界
1e6a3f1f0b release: Update debug iOS library build 2024-12-12 14:51:47 +08:00
128 changed files with 2734 additions and 1359 deletions

19
.fpm Normal file
View File

@@ -0,0 +1,19 @@
-s dir
--name sing-box
--category net
--license GPLv3-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
release/config/config.json=/etc/sing-box/config.json
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

View File

@@ -7,11 +7,6 @@ on:
description: "Version name"
required: true
type: string
prerelease:
description: "Is prerelease"
required: true
type: boolean
default: true
build:
description: "Build type"
required: true
@@ -28,10 +23,6 @@ on:
- tvOS
- macOS-standalone
- publish-android
macos_project_version:
description: "macOS project version"
required: false
type: string
push:
branches:
- main-next
@@ -47,7 +38,6 @@ jobs:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
prerelease: ${{ steps.outputs.outputs.prerelease }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -56,23 +46,20 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "prerelease=${{ inputs.prerelease }}"
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --nightly
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
build:
name: Build binary
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
@@ -81,141 +68,174 @@ jobs:
- calculate_version
strategy:
matrix:
os: [ linux, windows, darwin, android ]
arch: [ "386", amd64, arm64 ]
legacy_go: [ false ]
include:
- name: linux_386
goos: linux
goarch: 386
- name: linux_amd64
goos: linux
goarch: amd64
- name: linux_arm64
goos: linux
goarch: arm64
- name: linux_arm
goos: linux
goarch: arm
goarm: 6
- name: linux_arm_v7
goos: linux
goarch: arm
goarm: 7
- name: linux_s390x
goos: linux
goarch: s390x
- name: linux_riscv64
goos: linux
goarch: riscv64
- name: linux_mips64le
goos: linux
goarch: mips64le
- name: windows_amd64
goos: windows
goarch: amd64
require_legacy_go: true
- name: windows_386
goos: windows
goarch: 386
require_legacy_go: true
- name: windows_arm64
goos: windows
goarch: arm64
- name: darwin_arm64
goos: darwin
goarch: arm64
- name: darwin_amd64
goos: darwin
goarch: amd64
require_legacy_go: true
- name: android_arm64
goos: android
goarch: arm64
- name: android_arm
goos: android
goarch: arm
goarm: 7
- name: android_amd64
goos: android
goarch: amd64
- name: android_386
goos: android
goarch: 386
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
- { os: windows, arch: "386", legacy_go: true }
- { os: windows, arch: amd64, legacy_go: true }
- { os: darwin, arch: amd64, legacy_go: true }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
exclude:
- { os: darwin, arch: "386" }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
if: matrix.legacy_go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Cache legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
uses: actions/cache@v4
go-version: ~1.20
- name: Setup Go
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
with:
path: |
~/go/go1.20.14
key: go120
- name: Setup legacy Go
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz
mv go $HOME/go/go1.20.14
go-version: ^1.24
- name: Setup Android NDK
if: matrix.goos == 'android'
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
ndk-version: r28
local-cache: true
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.goos != 'android'
run: |-
goreleaser release --clean --split
if: matrix.os != 'android'
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOPATH: ${{ env.HOME }}/go
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Build Android
if: matrix.goos == 'android'
run: |-
if: matrix.os == 'android'
run: |
set -xeuo pipefail
go install -v ./cmd/internal/build
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
export CC='${{ matrix.ndk }}-clang'
export CXX="${CC}++"
mkdir -p dist
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
BUILD_GOOS: ${{ matrix.goos }}
BUILD_GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CGO_ENABLED: "1"
BUILD_GOOS: ${{ matrix.os }}
BUILD_GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Set name
run: |-
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
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 install -y debsigs
fpm -t deb \
-v "$PKG_VERSION" \
-p "dist/${PKG_NAME}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
fpm -t rpm \
-v "$PKG_VERSION" \
-p "dist/${PKG_NAME}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Package Pacman
if: matrix.pacman != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y libarchive-tools
fpm -t pacman \
-v "$PKG_VERSION" \
-p "dist/${PKG_NAME}.pkg.tar.zst" \
--architecture ${{ matrix.pacman }} \
dist/sing-box=/usr/bin/sing-box
- name: Archive
run: |
set -xeuo pipefail
cd dist
mkdir -p "${DIR_NAME}"
cp ../LICENSE "${DIR_NAME}"
if [ '${{ matrix.os }}' = 'windows' ]; then
cp sing-box "${DIR_NAME}/sing-box.exe"
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
else
cp sing-box "${DIR_NAME}"
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
fi
rm -r "${DIR_NAME}"
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.name }}
path: 'dist'
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
build_android:
name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
@@ -231,19 +251,20 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
ndk-version: r28
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
run: |-
make lib_install
@@ -253,12 +274,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: needs.calculate_version.outputs.prerelease == 'false'
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: needs.calculate_version.outputs.prerelease == 'true'
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
@@ -267,9 +288,16 @@ jobs:
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build
- name: Update version
if: github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci
- name: Update nightly version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci --nightly
- name: Build
run: |-
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
@@ -279,13 +307,11 @@ jobs:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
mkdir -p dist
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-android-apks
@@ -305,19 +331,20 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
ndk-version: r28
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
run: |-
make lib_install
@@ -327,12 +354,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: needs.calculate_version.outputs.prerelease == 'false'
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: needs.calculate_version.outputs.prerelease == 'true'
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
@@ -354,67 +381,38 @@ jobs:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
build_apple_library:
name: Build Apple library
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
runs-on: macos-15
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Xcode
run: |-
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_ios
- name: Upload library
uses: actions/upload-artifact@v4
with:
name: library-apple
path: 'Libbox.xcframework'
build_apple:
name: Build Apple clients
runs-on: macos-15
needs:
- calculate_version
- build_apple_library
strategy:
matrix:
include:
- name: iOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
platform: ios
scheme: SFI
destination: 'generic/platform=iOS'
archive: build/SFI.xcarchive
upload: SFI/Upload.plist
- name: macOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
platform: macos
scheme: SFM
destination: 'generic/platform=macOS'
archive: build/SFM.xcarchive
upload: SFI/Upload.plist
- name: tvOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
platform: tvos
scheme: SFT
destination: 'generic/platform=tvOS'
archive: build/SFT.xcarchive
upload: SFI/Upload.plist
- name: macOS-standalone
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
platform: macos
scheme: SFM.System
destination: 'generic/platform=macOS'
archive: build/SFM.System.xcarchive
@@ -431,23 +429,28 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Xcode
if: matrix.if
go-version: ^1.24
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Set tag
if: matrix.if
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Checkout main branch
if: matrix.if && needs.calculate_version.outputs.prerelease == 'false'
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/apple
git checkout main
- name: Checkout dev branch
if: matrix.if && needs.calculate_version.outputs.prerelease == 'true'
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
cd clients/apple
git checkout dev
@@ -466,18 +469,22 @@ jobs:
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_PATH"
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
xcrun notarytool store-credentials "notarytool-password" \
--key $ASC_KEY_PATH \
--key-id $ASC_KEY_ID \
--issuer $ASC_KEY_ISSUER_ID
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
env:
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
@@ -486,12 +493,19 @@ jobs:
ASC_KEY: ${{ secrets.ASC_KEY }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Download library
- name: Build library
if: matrix.if
uses: actions/download-artifact@v4
with:
name: library-apple
path: clients/apple/Libbox.xcframework
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
mv Libbox.xcframework clients/apple
- name: Update macOS version
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
run: |-
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
- name: Build
if: matrix.if
run: |-
@@ -503,27 +517,25 @@ jobs:
-destination "${{ matrix.destination }}" \
-archivePath "${{ matrix.archive }}" \
-allowProvisioningUpdates \
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
env:
MACOS_PROJECT_VERSION: ${{ inputs.macos_project_version }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Upload to App Store Connect
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
cd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.upload }} \
-allowProvisioningUpdates \
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Publish to TestFlight
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
run: |-
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
- name: Build image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
@@ -545,10 +557,10 @@ jobs:
cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs
popd
mkdir -p dist/release
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
mkdir -p dist
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
@@ -557,7 +569,7 @@ jobs:
path: 'dist'
upload:
name: Upload builds
if: always() && github.event_name == 'workflow_dispatch' && inputs.build != 'publish-android'
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
runs-on: ubuntu-latest
needs:
- calculate_version
@@ -569,12 +581,6 @@ jobs:
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
install-only: true
- name: Cache ghr
uses: actions/cache@v4
id: cache-ghr
@@ -591,25 +597,25 @@ jobs:
go install -v .
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Merge builds
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
run: |-
goreleaser continue --merge --skip publish
mkdir -p dist/release
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload builds
if: ${{ env.PUBLISHED == 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds
if: ${{ env.PUBLISHED != 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -28,10 +28,11 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout=30m
install-mode: binary
install-mode: binary
verify: false

View File

@@ -1,13 +1,22 @@
name: Release to Linux repository
name: Build Linux Packages
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
release:
types:
- published
jobs:
build:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -16,23 +25,161 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Extract signing key
go-version: ^1.24
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
local-cache: true
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set mtime
run: |-
TZ=UTC touch -t '197001010000' dist/sing-box
- name: Set name
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
run: |-
echo "NAME=sing-box" >> "$GITHUB_ENV"
- name: Set beta name
if: contains(needs.calculate_version.outputs.version, '-')
run: |-
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
- name: Set version
run: |-
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
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 install -y debsigs
fpm -t deb \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release
uses: goreleaser/goreleaser-action@v6
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
fpm -t rpm \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
distribution: goreleaser-pro
version: latest
args: release -f .goreleaser.fury.yaml --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
upload:
name: Upload builds
runs-on: ubuntu-latest
needs:
- calculate_version
- build
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish packages
run: |-
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/

View File

@@ -22,6 +22,16 @@ linters-settings:
run:
go: "1.23"
build-tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
issues:
exclude-dirs:

View File

@@ -52,7 +52,7 @@ builds:
env:
- CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go1.20.14
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
targets:
- windows_amd64_v1
- windows_386
@@ -95,10 +95,12 @@ archives:
builds:
- main
- android
format: tar.gz
formats:
- tar.gz
format_overrides:
- goos: windows
format: zip
formats:
- zip
wrap_in_directory: true
files:
- LICENSE

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box

View File

@@ -28,7 +28,7 @@ ci_build:
go build $(MAIN_PARAMS) $(MAIN)
generate_completions:
go run -v --tags generate,generate_completions $(MAIN)
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
@@ -182,10 +182,22 @@ release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
go run ./cmd/internal/update_apple_version
update_macos_version:
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple_beta: update_apple_version release_ios release_macos release_tvos
publish_testflight:
go run -v ./cmd/internal/app_store_connect publish_testflight
prepare_app_store:
go run -v ./cmd/internal/app_store_connect prepare_app_store
publish_app_store:
go run -v ./cmd/internal/app_store_connect publish_app_store
test:
@go test -v ./... && \
cd test && \
@@ -204,11 +216,11 @@ lib_android:
lib_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_ios:
go run ./cmd/internal/build_libbox -target ios
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_ios_debug:
go run ./cmd/internal/build_libbox -target ios -debug
lib_ios:
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
lib:
go run ./cmd/internal/build_libbox -target android

View File

@@ -39,17 +39,17 @@ type CacheFile interface {
StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error
LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedRuleSet) error
LoadRuleSet(tag string) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error
}
type SavedRuleSet struct {
type SavedBinary struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
@@ -70,7 +70,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
return buffer.Bytes(), nil
}
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)

View File

@@ -53,10 +53,11 @@ type InboundContext struct {
// sniffer
Protocol string
Domain string
Client string
SniffContext any
Protocol string
Domain string
Client string
SniffContext any
PacketSniffError error
// cache
@@ -72,7 +73,7 @@ type InboundContext struct {
UDPConnect bool
UDPTimeout time.Duration
NetworkStrategy C.NetworkStrategy
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration

View File

@@ -25,10 +25,11 @@ type NetworkManager interface {
PackageManager() tun.PackageManager
WIFIState() WIFIState
ResetNetwork()
UpdateWIFIState()
}
type NetworkOptions struct {
NetworkStrategy C.NetworkStrategy
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration

31
box.go
View File

@@ -14,6 +14,7 @@ import (
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/cachefile"
@@ -149,6 +150,14 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "initialize router")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
}
for i, endpointOptions := range options.Endpoints {
var tag string
if endpointOptions.Tag != "" {
@@ -156,7 +165,15 @@ func New(options Options) (*Box, error) {
} else {
tag = F.ToString(i)
}
err = endpointManager.Create(ctx,
endpointCtx := ctx
if tag != "" {
// TODO: remove this
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = endpointManager.Create(
endpointCtx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
@@ -174,7 +191,8 @@ func New(options Options) (*Box, error) {
} else {
tag = F.ToString(i)
}
err = inboundManager.Create(ctx,
err = inboundManager.Create(
ctx,
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag,
@@ -254,13 +272,12 @@ func New(options Options) (*Box, error) {
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
if err != nil {
return nil, E.Cause(err, "create NTP service")
}
timeService := ntp.NewService(ntp.Options{
ntpService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
@@ -268,8 +285,8 @@ func New(options Options) (*Box, error) {
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
service.MustRegister[ntp.TimeService](ctx, timeService)
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
}
return &Box{
network: networkManager,
@@ -391,7 +408,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.inbound, s.outbound, s.router, s.connection, s.network,
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network,
)
for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -0,0 +1,450 @@
package main
import (
"context"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func main() {
ctx := context.Background()
switch os.Args[1] {
case "next_macos_project_version":
err := fetchMacOSVersion(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_testflight":
err := publishTestflight(ctx)
if err != nil {
log.Fatal(err)
}
case "cancel_app_store":
err := cancelAppStore(ctx, os.Args[2])
if err != nil {
log.Fatal(err)
}
case "prepare_app_store":
err := prepareAppStore(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_app_store":
err := publishAppStore(ctx)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("unknown action: ", os.Args[1])
}
}
const (
appID = "6673731168"
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
)
func createClient(expireDuration time.Duration) *asc.Client {
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
if err != nil {
log.Fatal(err)
}
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
if err != nil {
log.Fatal(err)
}
return asc.NewClient(tokenConfig.Client())
}
func fetchMacOSVersion(ctx context.Context) error {
client := createClient(time.Minute)
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{"MAC_OS"},
})
if err != nil {
return err
}
var versionID string
findVersion:
for _, version := range versions.Data {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStateReadyForSale,
asc.AppStoreVersionStatePendingDeveloperRelease:
versionID = version.ID
break findVersion
}
}
if versionID == "" {
return E.New("no version found")
}
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
if err != nil {
return err
}
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
return nil
}
func publishTestflight(ctx context.Context) error {
tagVersion, err := build_shared.ReadTagVersion()
if err != nil {
return err
}
tag := tagVersion.VersionString()
client := createClient(10 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
if err != nil {
return err
}
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
return it.ID
})
var platforms []asc.Platform
if len(os.Args) == 3 {
switch os.Args[2] {
case "ios":
platforms = []asc.Platform{asc.PlatformIOS}
case "macos":
platforms = []asc.Platform{asc.PlatformMACOS}
case "tvos":
platforms = []asc.Platform{asc.PlatformTVOS}
default:
return E.New("unknown platform: ", os.Args[2])
}
} else {
platforms = []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
}
}
for _, platform := range platforms {
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
build := builds.Data[0]
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
F.ToString("sing-box ", tagVersion.String()),
))
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err
}
}
break
}
}
return nil
}
func cancelAppStore(ctx context.Context, platform string) error {
switch platform {
case "ios":
platform = string(asc.PlatformIOS)
case "macos":
platform = string(asc.PlatformMACOS)
case "tvos":
platform = string(asc.PlatformTVOS)
}
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for {
log.Info(platform, " list versions")
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if isRetryable(response) {
continue
} else if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
if version.ID == "" {
return nil
}
log.Info(platform, " ", tag, " get submission")
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if response != nil && response.StatusCode == http.StatusNotFound {
return nil
}
if isRetryable(response) {
continue
} else if err != nil {
return err
}
log.Info(platform, " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
return nil
}
}
func prepareAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
log.Info(string(platform), " ", tag, " list builds")
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
if len(builds.Data) == 0 {
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {
log.Info(string(platform), " ", tag, " create version")
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
Platform: platform,
VersionString: tag,
}, appID, buildID)
if err != nil {
return err
}
version = newVersion.Data
} else {
log.Info(string(platform), " ", tag, " check build")
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
if submission != nil {
log.Info(string(platform), " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
time.Sleep(5 * time.Second)
}
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
log.Info(string(platform), " ", tag, " update build")
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
response.Write(os.Stderr)
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
}
} else {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
continue
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
}
}
log.Info(string(platform), " ", tag, " list localization")
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Info(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
})
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " create submission")
fixSubmit:
for {
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
if err != nil {
switch response.StatusCode {
case http.StatusInternalServerError:
continue
default:
return err
}
}
switch response.StatusCode {
case http.StatusCreated:
break fixSubmit
default:
return err
}
}
}
return nil
}
func publishAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
log.Fatal(string(platform), " ", tag, " not submitted")
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview:
log.Warn(string(platform), " ", tag, " waiting for review")
continue
case asc.AppStoreVersionStatePendingDeveloperRelease:
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
if err != nil {
return err
}
}
return nil
}
func isRetryable(response *asc.Response) bool {
if response == nil {
return false
}
switch response.StatusCode {
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
return true
default:
return false
}
}

View File

@@ -18,11 +18,13 @@ import (
var (
debugEnabled bool
target string
platform string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
}
func main() {
@@ -33,8 +35,8 @@ func main() {
switch target {
case "android":
buildAndroid()
case "ios":
buildiOS()
case "apple":
buildApple()
}
}
@@ -81,7 +83,9 @@ func buildAndroid() {
}
var bindTarget string
if debugEnabled {
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "android/arm64"
} else {
bindTarget = "android"
@@ -129,12 +133,14 @@ func buildAndroid() {
}
}
func buildiOS() {
func buildApple() {
var bindTarget string
if debugEnabled {
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "ios"
} else {
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
bindTarget = "ios,tvos,macos"
}
args := []string{

View File

@@ -48,7 +48,7 @@ func FindSDK() {
}
func findNDK() bool {
const fixedVersion = "28.0.12674087"
const fixedVersion = "28.0.13004108"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath

View File

@@ -36,11 +36,3 @@ func ReadTagVersion() (badversion.Version, error) {
}
return version, nil
}
func IsDevBranch() bool {
branch, err := shell.Exec("git", "branch", "--show-current").ReadOutput()
if err != nil {
return false
}
return branch == "dev-next"
}

View File

@@ -5,57 +5,57 @@ import (
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format"
)
var nightly bool
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
if nightly {
version, err := build_shared.ReadTagVersionRev()
var (
versionStr string
err error
)
if flagRunNightly {
var version badversion.Version
version, err = build_shared.ReadTagVersionRev()
if err == nil {
if version.PreReleaseIdentifier == "" {
version.Patch++
}
versionStr = version.String()
}
} else {
versionStr, err = build_shared.ReadTag()
}
if flagRunInCI {
if err != nil {
log.Fatal(err)
}
var (
versionStr string
isPrerelease bool
)
if version.PreReleaseIdentifier != "" {
isPrerelease = true
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
if build_shared.IsDevBranch() {
isPrerelease = true
}
err = setGitHubOutput("version", versionStr)
if err != nil {
log.Fatal(err)
}
err = setGitHubOutput("prerelease", F.ToString(isPrerelease))
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
} else {
tag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(tag + "\n")
os.Stdout.WriteString(versionStr + "\n")
}
}
}
func setGitHubOutput(name string, value string) error {
func setGitHubEnv(name string, value string) error {
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err

View File

@@ -13,10 +13,14 @@ import (
"github.com/sagernet/sing/common"
)
var flagRunInCI bool
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
@@ -46,21 +50,23 @@ func main() {
switch propPair[0] {
case "VERSION_NAME":
if propPair[1] != newVersion {
log.Info("updated version from ", propPair[1], " to ", newVersion)
versionUpdated = true
propPair[1] = newVersion
log.Info("updated version to ", newVersion)
}
case "GO_VERSION":
if propPair[1] != runtime.Version() {
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
goVersionUpdated = true
propPair[1] = runtime.Version()
log.Info("updated Go version to ", runtime.Version())
}
}
}
if !(versionUpdated || goVersionUpdated) {
log.Info("version not changed")
return
} else if flagRunInCI && !flagRunNightly {
log.Fatal("version changed, commit changes first.")
}
for _, propPair := range propsList {
switch propPair[0] {

View File

@@ -30,7 +30,7 @@ func init() {
}
func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}

View File

@@ -18,7 +18,7 @@ import (
)
var commandMerge = &cobra.Command{
Use: "merge <output>",
Use: "merge <output-path>",
Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0])

View File

@@ -0,0 +1,162 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var (
ruleSetPaths []string
ruleSetDirectories []string
)
var commandRuleSetMerge = &cobra.Command{
Use: "merge <output-path>",
Short: "Merge rule-set source files",
Run: func(cmd *cobra.Command, args []string) {
err := mergeRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
Args: cobra.ExactArgs(1),
}
func init() {
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
commandRuleSet.AddCommand(commandRuleSetMerge)
}
type RuleSetEntry struct {
content []byte
path string
options option.PlainRuleSetCompat
}
func readRuleSetAt(path string) (*RuleSetEntry, error) {
var (
configContent []byte
err error
)
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(path)
}
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
return &RuleSetEntry{
content: configContent,
path: path,
options: options,
}, nil
}
func readRuleSet() ([]*RuleSetEntry, error) {
var optionsList []*RuleSetEntry
for _, path := range ruleSetPaths {
optionsEntry, err := readRuleSetAt(path)
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
for _, directory := range ruleSetDirectories {
entries, err := os.ReadDir(directory)
if err != nil {
return nil, E.Cause(err, "read rule-set directory at ", directory)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
continue
}
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
}
sort.Slice(optionsList, func(i, j int) bool {
return optionsList[i].path < optionsList[j].path
})
return optionsList, nil
}
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
optionsList, err := readRuleSet()
if err != nil {
return option.PlainRuleSetCompat{}, err
}
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var optionVersion uint8
for _, options := range optionsList {
if optionVersion < options.options.Version {
optionVersion = options.options.Version
}
}
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
}
}
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
}
mergedOptions.Version = optionVersion
return mergedOptions, nil
}
func mergeRuleSet(outputPath string) error {
mergedOptions, err := readRuleSetAndMerge()
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(mergedOptions)
if err != nil {
return E.Cause(err, "encode config")
}
if existsContent, err := os.ReadFile(outputPath); err != nil {
if string(existsContent) == buffer.String() {
return nil
}
}
err = rw.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

@@ -61,14 +61,15 @@ func upgradeRuleSet(sourcePath string) error {
log.Info("already up-to-date")
return nil
}
plainRuleSet, err := plainRuleSetCompat.Upgrade()
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
if err != nil {
return err
}
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSet)
err = encoder.Encode(plainRuleSetCompat)
if err != nil {
return E.Cause(err, "encode config")
}

View File

@@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error {
return err
}
http3Client = &http.Client{
Transport: &http3.RoundTripper{
Transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)

View File

@@ -4,7 +4,6 @@ import (
"context"
"os"
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
@@ -58,7 +57,7 @@ func syncTime() error {
return err
}
if commandSyncTimeWrite {
err = settings.SetSystemTime(response.Time)
err = ntp.SetSystemTime(response.Time)
if err != nil {
return E.Cause(err, "write time to system")
}

View File

@@ -2,13 +2,16 @@ package dialer
import (
"context"
"errors"
"net"
"net/netip"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
@@ -16,6 +19,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
var (
@@ -24,31 +28,36 @@ var (
)
type DefaultDialer struct {
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time]
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time]
}
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
platformInterface := service.FromContext[platform.Interface](ctx)
var (
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
)
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
@@ -74,31 +83,38 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
}
}
if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault {
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil {
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
}
networkStrategy = C.NetworkStrategy(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
networkFallbackDelay = time.Duration(options.NetworkFallbackDelay)
if networkManager == nil || !networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
if disableDefaultBind || options.TCPFastOpen {
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`")
}
}
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
if networkManager != nil {
defaultOptions := networkManager.DefaultOptions()
if options.BindInterface == "" {
if !disableDefaultBind {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() {
if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
networkFallbackDelay = defaultOptions.FallbackDelay
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 {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
@@ -172,9 +188,6 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
listener.Control = control.Append(listener.Control, controlFn)
}
}
if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen {
return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`")
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
@@ -184,19 +197,20 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
return nil, err
}
return &DefaultDialer{
dialer4: tcpDialer4,
dialer6: tcpDialer6,
udpDialer4: udpDialer4,
udpDialer6: udpDialer6,
udpListener: listener,
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
isWireGuardListener: options.IsWireGuardListener,
networkManager: networkManager,
networkStrategy: networkStrategy,
networkType: networkType,
fallbackNetworkType: fallbackNetworkType,
networkFallbackDelay: networkFallbackDelay,
dialer4: tcpDialer4,
dialer6: tcpDialer6,
udpDialer4: udpDialer4,
udpDialer6: udpDialer6,
udpListener: listener,
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
isWireGuardListener: options.IsWireGuardListener,
networkManager: networkManager,
networkStrategy: networkStrategy,
defaultNetworkStrategy: defaultNetworkStrategy,
networkType: networkType,
fallbackNetworkType: fallbackNetworkType,
networkFallbackDelay: networkFallbackDelay,
}, nil
}
@@ -204,7 +218,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
if !address.IsValid() {
return nil, E.New("invalid address")
}
if d.networkStrategy == C.NetworkStrategyDefault {
if d.networkStrategy == nil {
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
@@ -223,12 +237,21 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
}
}
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if strategy == C.NetworkStrategyDefault {
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
return d.DialContext(ctx, network, address)
}
if !d.networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
}
var dialer net.Dialer
if N.NetworkName(network) == N.NetworkTCP {
@@ -243,12 +266,18 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
err error
)
if !fastFallback {
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
}
if err != nil {
return nil, err
// bind interface failed on legacy xiaomi systems
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
d.networkStrategy = nil
return d.DialContext(ctx, network, address)
} else {
return nil, err
}
}
if !fastFallback && !isPrimary {
d.networkLastFallback.Store(time.Now())
@@ -257,7 +286,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == C.NetworkStrategyDefault {
if d.networkStrategy == nil {
if destination.IsIPv6() {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
@@ -270,18 +299,37 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
}
}
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == C.NetworkStrategyDefault {
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
return d.ListenPacket(ctx, destination)
}
if !d.networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
}
network := N.NetworkUDP
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
network += "4"
}
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil {
// bind interface failed on legacy xiaomi systems
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
d.networkStrategy = nil
return d.ListenPacket(ctx, destination)
} else {
return nil, err
}
}
return trackPacketConn(packetConn, nil)
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {

View File

@@ -18,6 +18,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -31,16 +32,18 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil {
select {
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
case <-returned:
}
} else {
select {
case results <- dialResult{Conn: conn}:
case results <- dialResult{Conn: conn, primary: primary}:
case <-returned:
conn.Close()
}
@@ -89,6 +92,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -103,16 +107,18 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil {
select {
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
case <-returned:
}
} else {
select {
case results <- dialResult{Conn: conn}:
case results <- dialResult{Conn: conn, primary: primary}:
case <-returned:
if primary && time.Since(startAt) <= fallbackDelay {
resetFastFallback(time.Time{})
@@ -149,24 +155,29 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
var errors []error
for _, primaryInterface := range primaryInterfaces {
perNetListener := listener
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index {
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
}
conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil {
return conn, nil
}
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")"))
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")"))
}
for _, fallbackInterface := range fallbackInterfaces {
perNetListener := listener
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index {
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
}
conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil {
return conn, nil
}
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")"))
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")"))
}
return nil, E.Errors(errors...)
}
@@ -177,44 +188,57 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
case C.NetworkStrategyDefault:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
}
}
} else {
primaryInterfaces = interfaces
}
} else {
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
case C.NetworkStrategyHybrid:
if len(interfaceType) == 0 {
primaryInterfaces = interfaces
} else {
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
case C.NetworkStrategyFallback:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
break
}
}
} else {
primaryInterfaces = interfaces
}
} else {
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
return it.Index == iif.Index
})
})
} else {
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
})
}
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
})
}
return primaryInterfaces, fallbackInterfaces
}

View File

@@ -13,7 +13,13 @@ import (
N "github.com/sagernet/sing/common/network"
)
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
@@ -38,7 +44,14 @@ func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, des
return nil, E.Errors(errors...)
}
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -116,7 +129,13 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
}
}
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}

View File

@@ -17,16 +17,15 @@ import (
)
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
if options.IsWireGuardListener {
return NewDefault(networkManager, options)
return NewDefault(ctx, options)
}
var (
dialer N.Dialer
err error
)
if options.Detour == "" {
dialer, err = NewDefault(networkManager, options)
dialer, err = NewDefault(ctx, options)
if err != nil {
return nil, err
}
@@ -37,9 +36,6 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
}
dialer = NewDetour(outboundManager, options.Detour)
}
if networkManager == nil {
return NewDefault(networkManager, options)
}
if options.Detour == "" {
router := service.FromContext[adapter.Router](ctx)
if router != nil {
@@ -58,11 +54,10 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
if options.Detour != "" {
return nil, E.New("`detour` is not supported in direct context")
}
networkManager := service.FromContext[adapter.NetworkManager](ctx)
if options.IsWireGuardListener {
return NewDefault(networkManager, options)
return NewDefault(ctx, options)
}
dialer, err := NewDefault(networkManager, options)
dialer, err := NewDefault(ctx, options)
if err != nil {
return nil, err
}
@@ -77,11 +72,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
type ParallelInterfaceDialer interface {
N.Dialer
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
}
type ParallelNetworkDialer interface {
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
}

View File

@@ -106,7 +106,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
@@ -134,7 +134,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
}
}
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}

View File

@@ -1,12 +0,0 @@
//go:build !(windows || linux || darwin)
package settings
import (
"os"
"time"
)
func SetSystemTime(nowTime time.Time) error {
return os.ErrInvalid
}

View File

@@ -1,14 +0,0 @@
//go:build linux || darwin
package settings
import (
"time"
"golang.org/x/sys/unix"
)
func SetSystemTime(nowTime time.Time) error {
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
return unix.Settimeofday(&timeVal)
}

View File

@@ -1,32 +0,0 @@
package settings
import (
"time"
"unsafe"
"golang.org/x/sys/windows"
)
func SetSystemTime(nowTime time.Time) error {
var systemTime windows.Systemtime
systemTime.Year = uint16(nowTime.Year())
systemTime.Month = uint16(nowTime.Month())
systemTime.Day = uint16(nowTime.Day())
systemTime.Hour = uint16(nowTime.Hour())
systemTime.Minute = uint16(nowTime.Minute())
systemTime.Second = uint16(nowTime.Second())
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
proc := dllKernel32.NewProc("SetSystemTime")
_, _, err := proc.Call(
uintptr(unsafe.Pointer(&systemTime)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return err
}
return nil
}

View File

@@ -12,6 +12,26 @@ import (
"github.com/stretchr/testify/require"
)
func TestSniffQUICChromeNew(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("ca0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489ad89c322f75f9a383c90d126a0b21104cb519c2bb32e6a134e86896452e942b26c519b8c7ac9e4c99fae5e1f65cf08fb98443b30e4567932e8fb0789820d8f33037b59ac8113530258c9467dfb52489396dae01f099d28b234efa107fa411f2a1ffa2abe74988e03d662d4296024e95ce0fe1671724937157f77b84990478a2d4060676cf0827b4e8c600654111750414dafa0cccb332f3020c2922a015f445df5edc9c7d2d1ceea9fddcc9ff821c9183aa39a70da20fcc057579e1051c1c899148d6cf9d08b4919822082d040d1ce03ca4f216be6cb7ef03db6df0993ef1ccce5c8c648980554f41704526e1809d2545739f5872e75ec797db1c99f5682e2eda9363cb32aa367b7b363c782ddbacf874183cc15c8a2db068dd4093eebdd096ad33832a7939deb0a872279744f5a56dc001ba62fac973bf680f3b362bdd336add4dd102f462b773bf70bfce1921070a802a92025273a177186d1a643081b42175eb789ccddadb71033ef4feacbf6fd282ab622cf61669d73cda559e411c6ccdd8f003443b6933b7729b7a357aa4aa2fba0f365f829a4d497afb5dc2648a53bc9f3e786d955069d0a4781088a5463747dfe9958ea19ea444eae947ec6a67640955f710f93640084f3fbb8ad259b68dbc0ee0b7fab2d81bffd83ed8a6d33522dbfef43bec0a0fb4bdf1cb712dc4ced0680c0687fa240fd157baa232b1c84e14adce6421cf9270f9b3972f98fc67b344b8a4f1fb551e26f7f76d484ed9f8197f231dc5d9a44cc0ddce73d7f810a620851f4e97eb5037ab5135d7c3be5b80cc32d19910b8387aca64c93c02dc3e35238b78e6aff470722078982e58802844932b6041446bfdcc97ba640cbb86721bcd0f40f27b77aa6287ce5674ec1720134b9302875482c3269787e004b9edb483d44f326eef38c0e83cb46af96488c2e696bc2524567fb29c1e8edcd5a73615496d172d46a9d29e0505c0018b7bbb00165eca0389e09c4b1d73b6cc4a2f735a720650134a2e98e8105e20695cf231b92586237dfe0f99c897414e51c21627496276535f07abb53fb2b554376fe520fa45a3e944fd91dfe7a72aead08842b6b63d8edf861fb911954c83bd9a896eb9da4af5eff646455069d747facd4e77c254096843bff7c3e9031dbdf8dc37ea45f1122922fcbc322ec1378f3c7c1af0da62e1052e6210f1b23073f93a82d90e14cb20bc4501d487a1c848674d57a7c269b13590b3a99d8b8b4f6d0dfbd1d2cbbe7a32c0d5c84ae7ec438b0b19f3862d8fabaa828d06c7e3c6967405cd56a1ae90f38633e2ee0e3ecfca3df399fe12f029e0860a1a30da010300d0c94f0bf56091d00011488c1429928b21c739ebf50ba8be91116315d3173f6d2c56735722478c4d74392ba84d1727036b3d64e8c2263b0f33cb8086be587ca6b3940259c06afa2683868856529303ae12e91d7ca874568be7f2bfaa0656dfab0ed31ed90eaea10fb7f3433ec59a334abe6211d547fa0c825ac45d3691e749d15432008de83e9f6d98f368359137ae803d9189b3386f800c7c0cf4b615d1983cf82d9981a8105b60a80fe66c9b0d439b5ba153dd19e9e7483a01cf3b02b4597540b38e658d4eb8455e030b2bf2690bdd78c23f16fe5")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.NoError(t, err)
require.Equal(t, "www.google.com", metadata.Domain)
}
func TestSniffQUICChromium(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620")

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -34,7 +35,7 @@ func Skip(metadata *adapter.InboundContext) bool {
return false
}
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
if timeout == 0 {
timeout = C.ReadPayloadTimeout
}
@@ -55,7 +56,10 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
errors = nil
for _, sniffer := range sniffers {
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
return bytes.NewReader(it.Bytes())
})...)
err = sniffer(ctx, metadata, reader)
if err == nil {
return nil
}

View File

@@ -30,15 +30,14 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled {
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
return NewECHClient(ctx, serverAddress, options)
}
return NewECHClient(ctx, serverAddress, options)
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityClient(ctx, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(ctx, serverAddress, options)
} else {
return NewSTDClient(ctx, serverAddress, options)
}
return NewSTDClient(ctx, serverAddress, options)
}
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {

View File

@@ -64,6 +64,7 @@ type echConnWrapper struct {
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,

View File

@@ -7,6 +7,7 @@ import (
"encoding/binary"
"encoding/pem"
cftls "github.com/sagernet/cloudflare-tls"
E "github.com/sagernet/sing/common/exceptions"
"github.com/cloudflare/circl/hpke"
@@ -58,6 +59,7 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config
type echKeyConfigPair struct {
id uint8
key cftls.EXP_ECHKey
rawKey []byte
conf myECHKeyConfig
rawConf []byte
@@ -145,19 +147,23 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
pair.rawConf = b
secBuf, err := sec.MarshalBinary()
if err != nil {
return nil, E.Cause(err, "serialize ECH private key")
}
sk := []byte{}
sk = be.AppendUint16(sk, uint16(len(secBuf)))
sk = append(sk, secBuf...)
sk = be.AppendUint16(sk, uint16(len(b)))
sk = append(sk, b...)
cfECHKeys, err := UnmarshalECHKeys(sk)
cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk)
if err != nil {
return nil, E.Cause(err, "bug: can't parse generated ECH server key")
}
if len(cfECHKeys) != 1 {
return nil, E.New("bug: unexpected server key count")
}
pair.key = cfECHKeys[0]
pair.rawKey = sk
pairs = append(pairs, pair)

View File

@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
}
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
return &http3.RoundTripper{
return &http3.Transport{
TLSClientConfig: c.config,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {

View File

@@ -11,8 +11,8 @@ import (
"time"
)
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
if err != nil {
return nil, err
}
@@ -23,7 +23,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer
return &certificate, err
}
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
if timeFunc == nil {
timeFunc = time.Now
}
@@ -47,7 +47,11 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T
},
DNSNames: []string{serverName},
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if parent == nil {
parent = template
parentKey = key
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
if err != nil {
return
}

View File

@@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
return nil, E.New("reality verification failed")
}
return &utlsConnWrapper{uConn}, nil
return &realityClientConnWrapper{uConn}, nil
}
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
@@ -249,3 +249,36 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
}
return nil
}
type realityClientConnWrapper struct {
*utls.UConn
}
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func (c *realityClientConnWrapper) Upstream() any {
return c.UConn
}
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
// We fixed it by calling Close() directly.
func (c *realityClientConnWrapper) CloseWrite() error {
return c.Close()
}

View File

@@ -174,6 +174,7 @@ type realityConnWrapper struct {
func (c *realityConnWrapper) ConnectionState() ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
@@ -193,3 +194,9 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
func (c *realityConnWrapper) Upstream() any {
return c.Conn
}
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
// We fixed it by calling Close() directly.
func (c *realityConnWrapper) CloseWrite() error {
return c.Close()
}

View File

@@ -17,13 +17,12 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled {
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
return NewECHServer(ctx, logger, options)
}
return NewECHServer(ctx, logger, options)
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, logger, options)
} else {
return NewSTDServer(ctx, logger, options)
}
return NewSTDServer(ctx, logger, options)
}
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {

View File

@@ -4,25 +4,16 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"net"
"net/netip"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls"
"github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns"
)
var _ ConfigCompat = (*STDClientConfig)(nil)
type STDClientConfig struct {
config *tls.Config
}
@@ -55,63 +46,6 @@ func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
type STDECHClientConfig struct {
STDClientConfig
}
func (s *STDClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(s.config.ServerName),
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
},
}
dnsRouter := service.FromContext[adapter.Router](ctx)
response, err := dnsRouter.Exchange(ctx, message)
if err != nil {
return nil, E.Cause(err, "fetch ECH config list")
}
if response.Rcode != mDNS.RcodeSuccess {
return nil, E.Cause(dns.RCodeError(response.Rcode), "fetch ECH config list")
}
for _, rr := range response.Answer {
switch resource := rr.(type) {
case *mDNS.HTTPS:
for _, value := range resource.Value {
if value.Key().String() == "ech" {
echConfigList, err := base64.StdEncoding.DecodeString(value.String())
if err != nil {
return nil, E.Cause(err, "decode ECH config")
}
s.config.EncryptedClientHelloConfigList = echConfigList
}
}
}
}
return nil, E.New("no ECH config found in DNS records")
}
tlsConn, err := s.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}}
}
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
@@ -194,21 +128,5 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
}
tlsConfig.RootCAs = certPool
}
if options.ECH != nil && options.ECH.Enabled {
var echConfig []byte
if len(options.ECH.Config) > 0 {
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
} else if options.ECH.ConfigPath != "" {
content, err := os.ReadFile(options.ECH.ConfigPath)
if err != nil {
return nil, E.Cause(err, "read ECH config")
}
echConfig = content
}
if echConfig != nil {
tlsConfig.EncryptedClientHelloConfigList = echConfig
}
return &STDECHClientConfig{STDClientConfig{&tlsConfig}}, nil
}
return &STDClientConfig{&tlsConfig}, nil
}

View File

@@ -3,7 +3,6 @@ package tls
import (
"context"
"crypto/tls"
"encoding/pem"
"net"
"os"
"strings"
@@ -15,8 +14,6 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"golang.org/x/crypto/cryptobyte"
)
var errInsecureUnused = E.New("tls: insecure unused")
@@ -225,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {
@@ -241,31 +238,6 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
}
if options.ECH != nil && options.ECH.Enabled {
var echKey []byte
if len(options.ECH.Key) > 0 {
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
} else if options.ECH.KeyPath != "" {
content, err := os.ReadFile(options.ECH.KeyPath)
if err != nil {
return nil, E.Cause(err, "read ECH key")
}
echKey = content
} else {
return nil, E.New("missing ECH key")
}
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return nil, E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return nil, E.Cause(err, "parse ECH keys")
}
tlsConfig.EncryptedClientHelloKeys = echKeys
}
return &STDServerConfig{
config: tlsConfig,
logger: logger,
@@ -276,22 +248,3 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
keyPath: options.KeyPath,
}, nil
}
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
var keys []tls.EncryptedClientHelloKey
rawString := cryptobyte.String(raw)
for !rawString.Empty() {
var key tls.EncryptedClientHelloKey
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
return nil, E.New("error parsing private key")
}
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
return nil, E.New("error parsing config")
}
keys = append(keys, key)
}
if len(keys) == 0 {
return nil, E.New("empty ECH keys")
}
return keys, nil
}

View File

@@ -0,0 +1,22 @@
package tls
import (
"time"
"github.com/sagernet/sing/common/ntp"
)
type TimeServiceWrapper struct {
ntp.TimeService
}
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
if w.TimeService == nil {
return nil
}
return w.TimeService.TimeFunc()
}
func (w *TimeServiceWrapper) Upstream() any {
return w.TimeService
}

View File

@@ -69,6 +69,7 @@ type utlsConnWrapper struct {
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,

View File

@@ -1,8 +0,0 @@
//go:build android && debug
package constant
// TODO: remove after fixed
// https://github.com/golang/go/issues/68760
const FixAndroidStack = true

View File

@@ -1,5 +0,0 @@
//go:build !(android && debug)
package constant
const FixAndroidStack = false

View File

@@ -2,10 +2,198 @@
icon: material/alert-decagram
---
#### 1.11.0-beta.9
### 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)._
### 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)._
### 1.11.4
* Fixes and improvements
### 1.11.3
* Fixes and improvements
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
### 1.11.1
* Fixes and improvements
### 1.11.0
Important changes since 1.10:
* Introducing rule actions **1**
* Improve tun compatibility **3**
* Merge route options to route actions **4**
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
* Add multi network dialing **6**
* Add `cache_capacity` DNS option **7**
* Add `override_address` and `override_port` route options **8**
* Upgrade WireGuard outbound to endpoint **9**
* Add UDP GSO support for WireGuard
* Make GSO adaptive **10**
* Add UDP timeout route option **11**
* Add more masquerade options for hysteria2 **12**
* Add `rule-set merge` command
* Add port hopping support for Hysteria2 **13**
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
**1**:
New rule actions replace legacy inbound fields and special outbound fields,
and can be used for pre-matching **2**.
See [Rule](/configuration/route/rule/),
[Rule Action](/configuration/route/rule_action/),
[DNS Rule](/configuration/dns/rule/) and
[DNS Rule Action](/configuration/dns/rule_action/).
For migration, see
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
**2**:
Similar to Surge's pre-matching.
Specifically, new rule actions allow you to reject connections with
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
before connection established to improve tun's compatibility.
See [Rule Action](/configuration/route/rule_action/).
**3**:
When `gvisor` tun stack is enabled, even if the request passes routing,
if the outbound connection establishment fails,
the connection still does not need to be established and a TCP RST is replied.
**4**:
Route options in DNS route actions will no longer be considered deprecated,
see [DNS Route Action](/configuration/dns/rule_action/).
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
see [Route Action](/configuration/route/rule_action/).
**5**:
When using in graphical clients, new routing rule items allow you to match on
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
**6**:
Similar to Surge's strategy.
New options allow you to connect using multiple network interfaces,
prefer or only use one type of interface,
and configure a timeout to fallback to other interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy),
[Rule Action](/configuration/route/rule_action/#network_strategy)
and [Route](/configuration/route/#default_network_strategy).
**7**:
See [DNS](/configuration/dns/#cache_capacity).
**8**:
See [Rule Action](/configuration/route/#override_address) and
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
**9**:
The new WireGuard endpoint combines inbound and outbound capabilities,
and the old outbound will be removed in sing-box 1.13.0.
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
**10**:
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
For TUN, GSO has been removed,
see [Deprecated](/deprecated/#gso-option-in-tun).
**11**:
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
**12**:
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
**13**:
See [Hysteria2](/configuration/outbound/hysteria2/).
**14**:
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
### 1.10.7
* Fixes and improvements
#### 1.11.0-beta.20
* Hysteria2 `ignore_client_bandwidth` behavior update **1**
* Fixes and improvements
**1**:
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
See [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth).
#### 1.11.0-beta.17
* Add port hopping support for Hysteria2 **1**
* Fixes and improvements
**1**:
See [Hysteria2](/configuration/outbound/hysteria2/).
#### 1.11.0-beta.14
* Allow adding route (exclude) address sets to routes **1**
* Fixes and improvements
**1**:
When `auto_redirect` is not enabled, directly add `route[_exclude]_address_set`
to tun routes (equivalent to `route[_exclude]_address`).
Note that it **doesn't work on the Android graphical client** due to
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
but otherwise it works fine on all command line clients and Apple platforms.
See [route_address_set](/configuration/inbound/tun/#route_address_set) and
[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
#### 1.11.0-beta.12
* Add `rule-set merge` command
* Fixes and improvements
#### 1.11.0-beta.3
* Add more masquerade options for hysteria2 **1**
@@ -15,10 +203,6 @@ icon: material/alert-decagram
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
### 1.10.3
* Fixes and improvements
#### 1.11.0-alpha.25
* Update quic-go to v0.48.2

View File

@@ -7,6 +7,10 @@ icon: material/apple
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
platform-specific function implementation, such as TUN transparent proxy implementation.
!!! failure ""
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).
## :material-graph: Requirements
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+

View File

@@ -4,7 +4,8 @@ icon: material/alert-decagram
!!! quote "Changes in sing-box 1.11.0"
:material-alert: [masquerade](#masquerade)
:material-alert: [masquerade](#masquerade)
:material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)
### Structure
@@ -75,9 +76,13 @@ Authentication password
#### ignore_client_bandwidth
Commands the client to use the BBR flow control algorithm instead of Hysteria CC.
*When `up_mbps` and `down_mbps` are not set*:
Conflict with `up_mbps` and `down_mbps`.
Commands clients to use the BBR CC instead of Hysteria CC.
*When `up_mbps` and `down_mbps` are set*:
Deny clients to use the BBR CC.
#### tls

View File

@@ -4,7 +4,8 @@ icon: material/alert-decagram
!!! quote "sing-box 1.11.0 中的更改"
:material-alert: [masquerade](#masquerade)
:material-alert: [masquerade](#masquerade)
:material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)
### 结构
@@ -72,9 +73,13 @@ Hysteria 用户
#### ignore_client_bandwidth
*当 `up_mbps` 和 `down_mbps` 未设定时*:
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
`up_mbps``down_mbps` 冲突。
*`up_mbps` 和 `down_mbps` 已设定时*:
禁止客户端使用 BBR 拥塞控制算法。
#### tls

View File

@@ -4,7 +4,9 @@ icon: material/alert-decagram
!!! quote "Changes in sing-box 1.11.0"
:material-delete-alert: [gso](#gso)
:material-delete-alert: [gso](#gso)
:material-alert-decagram: [route_address_set](#stack)
:material-alert-decagram: [route_exclude_address_set](#stack)
!!! quote "Changes in sing-box 1.10.0"
@@ -88,13 +90,13 @@ icon: material/alert-decagram
0
],
"include_uid_range": [
"1000-99999"
"1000:99999"
],
"exclude_uid": [
1000
],
"exclude_uid_range": [
"1000-99999"
"1000:99999"
],
"include_android_user": [
0,
@@ -248,7 +250,7 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route_address_set` and `route_exclude_address_set`.
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
`0x2023` is used by default.
@@ -256,7 +258,7 @@ Connection input mark used by `route_address_set` and `route_exclude_address_set
!!! question "Since sing-box 1.10.0"
Connection output mark used by `route_address_set` and `route_exclude_address_set`.
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
`0x2024` is used by default.
@@ -329,29 +331,55 @@ Exclude custom routes when `auto_route` is enabled.
#### route_address_set
!!! question "Since sing-box 1.10.0"
=== "With `auto_redirect` enabled"
!!! quote ""
!!! question "Since sing-box 1.10.0"
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
!!! quote ""
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Unmatched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Unmatched traffic will bypass the sing-box routes.
=== "Without `auto_redirect` enabled"
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
!!! question "Since sing-box 1.11.0"
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`.
Unmatched traffic will bypass the sing-box routes.
Note that it **doesn't work on the Android graphical client** due to
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
but otherwise it works fine on all command line clients and Apple platforms.
#### route_exclude_address_set
!!! question "Since sing-box 1.10.0"
=== "With `auto_redirect` enabled"
!!! quote ""
!!! question "Since sing-box 1.10.0"
!!! quote ""
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Matched traffic will bypass the sing-box routes.
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Matched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
=== "Without `auto_redirect` enabled"
!!! question "Since sing-box 1.11.0"
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`.
Matched traffic will bypass the sing-box routes.
Note that it **doesn't work on the Android graphical client** due to
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
but otherwise it works fine on all command line clients and Apple platforms.
#### endpoint_independent_nat

View File

@@ -4,7 +4,9 @@ icon: material/alert-decagram
!!! quote "sing-box 1.11.0 中的更改"
:material-delete-alert: [gso](#gso)
:material-delete-alert: [gso](#gso)
:material-alert-decagram: [route_address_set](#stack)
:material-alert-decagram: [route_exclude_address_set](#stack)
!!! quote "sing-box 1.10.0 中的更改"
@@ -88,13 +90,13 @@ icon: material/alert-decagram
0
],
"include_uid_range": [
"1000-99999"
"1000:99999"
],
"exclude_uid": [
1000
],
"exclude_uid_range": [
"1000-99999"
"1000:99999"
],
"include_android_user": [
0,
@@ -329,29 +331,53 @@ tun 接口的 IPv6 前缀。
#### route_address_set
!!! question "自 sing-box 1.10.0 起"
=== "`auto_redirect` 已启用"
!!! quote ""
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 已启用。
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突。
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 启用
=== "`auto_redirect` 启用"
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
!!! question "自 sing-box 1.11.0 起"
`route.default_mark``[dialOptions].routing_mark` 冲突
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`
不匹配的流量将绕过 sing-box 路由。
请注意,由于 Android VpnService 无法处理大量路由DeadSystemException
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
#### route_exclude_address_set
!!! question "自 sing-box 1.10.0 起"
=== "`auto_redirect` 已启用"
!!! quote ""
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 已启用。
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 已启用
将指定规则集中的目标 IP CIDR 规则添加到防火墙
匹配的流量将绕过 sing-box 路由。
将指定规则集中的目标 IP CIDR 规则添加到防火墙
匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突
`route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用"
!!! question "自 sing-box 1.11.0 起"
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`
匹配的流量将绕过 sing-box 路由。
请注意,由于 Android VpnService 无法处理大量路由DeadSystemException
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
#### endpoint_independent_nat

View File

@@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。
{
"log": {},
"dns": {},
"ntp": {},
"endpoints": [],
"inbounds": [],
"outbounds": [],
@@ -22,6 +23,7 @@ sing-box 使用 JSON 作为配置文件格式。
|----------------|------------------------|
| `log` | [日志](./log/) |
| `dns` | [DNS](./dns/) |
| `ntp` | [NTP](./ntp/) |
| `endpoints` | [端点](./endpoint/) |
| `inbounds` | [入站](./inbound/) |
| `outbounds` | [出站](./outbound/) |

View File

@@ -8,7 +8,7 @@ icon: material/delete-clock
### Structure
```json F
```json
{
"type": "block",
"tag": "block"

View File

@@ -1,3 +1,12 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [server_ports](#server_ports)
:material-plus: [hop_interval](#hop_interval)
### Structure
```json
@@ -7,6 +16,10 @@
"server": "127.0.0.1",
"server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
@@ -22,6 +35,10 @@
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
!!! warning "Difference from official Hysteria2"
The official Hysteria2 supports an authentication method called **userpass**,
@@ -44,6 +61,24 @@ The server address.
The server port.
Ignored if `server_ports` is set.
#### server_ports
!!! question "Since sing-box 1.11.0"
Server port range list.
Conflicts with `server_port`.
#### hop_interval
!!! question "Since sing-box 1.11.0"
Port hopping interval.
`30s` is used by default.
#### up_mbps, down_mbps
Max bandwidth, in Mbps.

View File

@@ -1,3 +1,12 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [server_ports](#server_ports)
:material-plus: [hop_interval](#hop_interval)
### 结构
```json
@@ -7,6 +16,10 @@
"server": "127.0.0.1",
"server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
@@ -22,6 +35,10 @@
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
!!! warning "与官方 Hysteria2 的区别"
官方程序支持一种名为 **userpass** 的验证方式,
@@ -42,6 +59,24 @@
服务器端口。
如果设置了 `server_ports`,则忽略此项。
#### server_ports
!!! question "自 sing-box 1.11.0 起"
服务器端口范围列表。
`server_port` 冲突。
#### hop_interval
!!! question "自 sing-box 1.11.0 起"
端口跳跃间隔。
默认使用 `30s`
#### up_mbps, down_mbps
最大带宽。

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.11.0 废弃"
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
!!! quote "sing-box 1.11.0 中的更改"

View File

@@ -31,6 +31,45 @@ Tag of target outbound.
See `route-options` fields below.
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### route-options
```json
@@ -109,45 +148,6 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
| 443 | `quic` |
| 3478 | `stun` |
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### sniff
```json

View File

@@ -27,6 +27,45 @@ icon: material/new-box
参阅下方的 `route-options` 字段。
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### route-options
```json
@@ -107,45 +146,6 @@ UDP 连接超时时间。
| 443 | `quic` |
| 3478 | `stun` |
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### sniff
```json

View File

@@ -74,7 +74,7 @@ Tag of rule-set.
==Required==
List of [Headless Rule](../headless-rule/).
List of [Headless Rule](./headless-rule/).
### Local or Remote Fields

View File

@@ -74,7 +74,7 @@ icon: material/new-box
==必填==
一组 [无头规则](../headless-rule/).
一组 [无头规则](./headless-rule/).
### 本地或远程字段

View File

@@ -31,12 +31,11 @@ The protocol version, `1` or `2`.
### Application support
| Project | UoT v1 | UoT v2 |
|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
| Project | UoT v1 | UoT v2 |
|--------------|----------------------|----------------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
### Protocol details
@@ -50,7 +49,13 @@ The client requests the magic address to the upper layer proxy protocol to indic
|------|----------|-------|--------|----------|
| u8 | variable | u16be | u16be | variable |
**ATYP / address / port**: Uses the SOCKS address format.
**ATYP / address / port**: Uses the SOCKS address format, but with different address types:
| ATYP | Address type |
|--------|--------------|
| `0x00` | IPv4 Address |
| `0x01` | IPv6 Address |
| `0x02` | Domain Name |
#### Protocol version 2

View File

@@ -108,7 +108,7 @@ Inbound fields are deprecated and can be replaced by rule actions.
!!! info "References"
[Listen Fields](/configuration/inbound/listen/) /
[Listen Fields](/configuration/shared/listen/) /
[Rule](/configuration/route/rule/) /
[Rule Action](/configuration/route/rule_action/) /
[DNS Rule](/configuration/dns/rule/) /

View File

@@ -284,8 +284,8 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
})
}
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
var savedSet adapter.SavedRuleSet
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
var savedSet adapter.SavedBinary
err := c.DB.View(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketRuleSet)
if bucket == nil {
@@ -303,7 +303,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
return &savedSet
}
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error {
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
return c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketRuleSet)
if err != nil {

View File

@@ -128,11 +128,8 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
if options.ExternalUI != "" {
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
chiRouter.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI)))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(Dir(s.externalUI))))
})
}
return s, nil

View File

@@ -0,0 +1,18 @@
package clashapi
import "net/http"
type Dir http.Dir
func (d Dir) Open(name string) (http.File, error) {
file, err := http.Dir(d).Open(name)
if err != nil {
return nil, err
}
return &fileWrapper{file}, nil
}
// workaround for #2345 #2596
type fileWrapper struct {
http.File
}

View File

@@ -41,7 +41,6 @@ func (s *Server) downloadExternalUI() error {
} else {
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
}
s.logger.Info("downloading external ui")
var detour adapter.Outbound
if s.externalUIDownloadDetour != "" {
outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour)
@@ -53,6 +52,7 @@ func (s *Server) downloadExternalUI() error {
outbound := s.outbound.Default()
detour = outbound
}
s.logger.Info("downloading external ui using outbound/", detour.Type(), "[", detour.Tag(), "]")
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
@@ -71,15 +71,15 @@ func (s *Server) downloadExternalUI() error {
if response.StatusCode != http.StatusOK {
return E.New("download external ui failed: ", response.Status)
}
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
err = s.downloadZIP(response.Body, s.externalUI)
if err != nil {
removeAllInDirectory(s.externalUI)
}
return err
}
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, name)
func (s *Server) downloadZIP(body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip")
if err != nil {
return err
}

View File

@@ -1,8 +1,11 @@
package deprecated
import (
"fmt"
"github.com/sagernet/sing-box/common/badversion"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/locale"
F "github.com/sagernet/sing/common/format"
"golang.org/x/mod/semver"
@@ -34,15 +37,9 @@ func (n Note) Impending() bool {
func (n Note) Message() string {
if n.MigrationLink != "" {
return F.ToString(
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
)
return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
} else {
return F.ToString(
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
" and will be removed in sing-box ", n.ScheduledVersion, ".",
)
return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
}
}

View File

@@ -7,7 +7,6 @@ import (
"path/filepath"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -114,7 +113,7 @@ func (c *CommandClient) Connect() error {
if err != nil {
return err
}
if C.FixAndroidStack {
if sFixAndroidStack {
go func() {
c.handler.Connected()
c.handler.InitializeClashMode(newIterator(modeList), currentMode)

View File

@@ -4,7 +4,6 @@ import (
"encoding/binary"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
@@ -18,19 +17,7 @@ func (c *CommandClient) ServiceReload() error {
if err != nil {
return err
}
var hasError bool
err = binary.Read(conn, binary.BigEndian, &hasError)
if err != nil {
return err
}
if hasError {
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}
return E.New(errorMessage)
}
return nil
return readError(conn)
}
func (s *CommandServer) handleServiceReload(conn net.Conn) error {
@@ -55,19 +42,7 @@ func (c *CommandClient) ServiceClose() error {
if err != nil {
return err
}
var hasError bool
err = binary.Read(conn, binary.BigEndian, &hasError)
if err != nil {
return nil
}
if hasError {
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return nil
}
return E.New(errorMessage)
}
return nil
return readError(conn)
}
func (s *CommandServer) handleServiceClose(conn net.Conn) error {

View File

@@ -1,7 +1,6 @@
package libbox
import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
@@ -56,8 +55,13 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
}
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
if C.FixAndroidStack {
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
if sFixAndroidStack {
done := make(chan struct{})
go func() {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
close(done)
}()
<-done
} else {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
}

View File

@@ -81,7 +81,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
}
func (s *BoxService) Start() error {
if C.FixAndroidStack {
if sFixAndroidStack {
var err error
done := make(chan struct{})
go func() {
@@ -148,10 +148,10 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
return nil, E.New("android: unsupported uid options")
return nil, E.New("platform: unsupported uid options")
}
if len(options.IncludeAndroidUser) > 0 {
return nil, E.New("android: unsupported android_user option")
return nil, E.New("platform: unsupported android_user option")
}
routeRanges, err := options.BuildAutoRouteRanges(true)
if err != nil {

View File

@@ -31,3 +31,7 @@ func (s *BoxService) Wake() {
func (s *BoxService) ResetNetwork() {
s.instance.Router().ResetNetwork()
}
func (s *BoxService) UpdateWIFIState() {
s.instance.Network().UpdateWIFIState()
}

View File

@@ -9,50 +9,67 @@ import (
"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"
)
var (
sBasePath string
sWorkingPath string
sTempPath string
sUserID int
sGroupID int
sTVOS bool
sBasePath string
sWorkingPath string
sTempPath string
sUserID int
sGroupID int
sTVOS bool
sFixAndroidStack bool
)
func init() {
debug.SetPanicOnFault(true)
}
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {
sBasePath = basePath
sWorkingPath = workingPath
sTempPath = tempPath
sUserID = os.Getuid()
sGroupID = os.Getgid()
sTVOS = isTVOS
os.MkdirAll(sWorkingPath, 0o777)
os.MkdirAll(sTempPath, 0o777)
type SetupOptions struct {
BasePath string
WorkingPath string
TempPath string
Username string
IsTVOS bool
FixAndroidStack bool
}
func SetupWithUsername(basePath string, workingPath string, tempPath string, username string) error {
sBasePath = basePath
sWorkingPath = workingPath
sTempPath = tempPath
sUser, err := user.Lookup(username)
if err != nil {
return err
func Setup(options *SetupOptions) error {
sBasePath = options.BasePath
sWorkingPath = options.WorkingPath
sTempPath = options.TempPath
if options.Username != "" {
sUser, err := user.Lookup(options.Username)
if err != nil {
return err
}
sUserID, _ = strconv.Atoi(sUser.Uid)
sGroupID, _ = strconv.Atoi(sUser.Gid)
} else {
sUserID = os.Getuid()
sGroupID = os.Getgid()
}
sUserID, _ = strconv.Atoi(sUser.Uid)
sGroupID, _ = strconv.Atoi(sUser.Gid)
sTVOS = options.IsTVOS
// TODO: remove after fixed
// https://github.com/golang/go/issues/68760
sFixAndroidStack = options.FixAndroidStack
os.MkdirAll(sWorkingPath, 0o777)
os.MkdirAll(sTempPath, 0o777)
os.Chown(sWorkingPath, sUserID, sGroupID)
os.Chown(sTempPath, sUserID, sGroupID)
if options.Username != "" {
os.Chown(sWorkingPath, sUserID, sGroupID)
os.Chown(sTempPath, sUserID, sGroupID)
}
return nil
}
func SetLocale(localeId string) {
locale.Set(localeId)
}
func Version() string {
return C.Version
}

View File

@@ -13,7 +13,7 @@ import (
type TunOptions interface {
GetInet4Address() RoutePrefixIterator
GetInet6Address() RoutePrefixIterator
GetDNSServerAddress() (string, error)
GetDNSServerAddress() (*StringBox, error)
GetMTU() int32
GetAutoRoute() bool
GetStrictRoute() bool
@@ -89,11 +89,11 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
return mapRoutePrefix(o.Inet6Address)
}
func (o *tunOptions) GetDNSServerAddress() (string, error) {
func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
return "", E.New("need one more IPv4 address for DNS hijacking")
return nil, E.New("need one more IPv4 address for DNS hijacking")
}
return o.Inet4Address[0].Addr().Next().String(), nil
return wrapString(o.Inet4Address[0].Addr().Next().String()), nil
}
func (o *tunOptions) GetMTU() int32 {

View File

@@ -0,0 +1,32 @@
package locale
var (
localeRegistry = make(map[string]*Locale)
current = defaultLocal
)
type Locale struct {
// deprecated messages for graphical clients
Locale string
DeprecatedMessage string
DeprecatedMessageNoLink string
}
var defaultLocal = &Locale{
Locale: "en_US",
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
}
func Current() *Locale {
return current
}
func Set(localeId string) bool {
locale, loaded := localeRegistry[localeId]
if !loaded {
return false
}
current = locale
return true
}

View File

@@ -0,0 +1,11 @@
package locale
var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么您的配置文件已过时且将很快不可用。请联系您的配置提供者以更新配置。"
func init() {
localeRegistry["zh_CN"] = &Locale{
Locale: "zh_CN",
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
}
}

40
go.mod
View File

@@ -6,47 +6,48 @@ require (
github.com/caddyserver/certmagic v0.20.0
github.com/cloudflare/circl v1.3.7
github.com/cretz/bine v0.2.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.3.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/mholt/acmez v1.2.0
github.com/miekg/dns v1.1.62
github.com/miekg/dns v1.1.63
github.com/oschwald/maxminddb-golang v1.12.0
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.48.2-beta.1
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.0-beta.6
github.com/sagernet/sing-dns v0.4.0-beta.1
github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.4.0-alpha.4
github.com/sagernet/sing v0.6.5
github.com/sagernet/sing-dns v0.4.0
github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.0
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
github.com/sagernet/sing-tun v0.6.0-beta.2
github.com/sagernet/sing-vmess v0.2.0-beta.1
github.com/sagernet/sing-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.1
github.com/sagernet/sing-vmess v0.2.0
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.5
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.29.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/mod v0.20.0
golang.org/x/net v0.31.0
golang.org/x/sys v0.27.0
golang.org/x/net v0.34.0
golang.org/x/sys v0.30.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
@@ -58,7 +59,9 @@ require (
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
@@ -66,6 +69,7 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -88,8 +92,8 @@ require (
github.com/vishvananda/netns v0.0.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect

85
go.sum
View File

@@ -4,6 +4,8 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -12,10 +14,12 @@ github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbe
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -32,16 +36,19 @@ github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@@ -67,12 +74,12 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
@@ -89,6 +96,8 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4=
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
@@ -105,29 +114,29 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+yfK8DQ8Jk=
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.0-beta.6 h1:IFnTCG06Z5rLMZJqw1ZmDncDl2N9gsVw0MGvgakrpg8=
github.com/sagernet/sing v0.6.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA=
github.com/sagernet/sing v0.6.5 h1:TBKTK6Ms0/MNTZm+cTC2hhKunE42XrNIdsxcYtWqeUU=
github.com/sagernet/sing v0.6.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc=
github.com/sagernet/sing-dns v0.4.0/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.0 h1:E4geazHk/UrJTXMlT+CBCKmn8V86RhtNeczWtfeoEFc=
github.com/sagernet/sing-quic v0.4.0/go.mod h1:c+CytOEyeN20KCTFIP8YQUkNDVFLSzjrEPqP7Hlnxys=
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-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
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.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo=
github.com/sagernet/sing-tun v0.6.1/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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
@@ -143,8 +152,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
@@ -163,18 +172,18 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -182,19 +191,21 @@ 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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.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=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=

View File

@@ -20,12 +20,16 @@ type ID struct {
}
func ContextWithNewID(ctx context.Context) context.Context {
return context.WithValue(ctx, (*idKey)(nil), ID{
return ContextWithID(ctx, ID{
ID: rand.Uint32(),
CreatedAt: time.Now(),
})
}
func ContextWithID(ctx context.Context, id ID) context.Context {
return context.WithValue(ctx, (*idKey)(nil), id)
}
func IDFromContext(ctx context.Context) (ID, bool) {
id, loaded := ctx.Value((*idKey)(nil)).(ID)
return id, loaded

View File

@@ -74,6 +74,7 @@ func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error {
default:
return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
}
return nil
}
err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m))
if err != nil {
@@ -111,11 +112,13 @@ type Hysteria2MasqueradeString struct {
type Hysteria2OutboundOptions struct {
DialerOptions
ServerOptions
UpMbps int `json:"up_mbps,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
BrutalDebug bool `json:"brutal_debug,omitempty"`
}

View File

@@ -65,25 +65,24 @@ type DialerOptionsWrapper interface {
}
type DialerOptions struct {
Detour string `json:"detour,omitempty"`
BindInterface string `json:"bind_interface,omitempty"`
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"`
RoutingMark FwMark `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"`
Detour string `json:"detour,omitempty"`
BindInterface string `json:"bind_interface,omitempty"`
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"`
RoutingMark FwMark `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"`
}
func (o *DialerOptions) TakeDialerOptions() DialerOptions {

View File

@@ -13,7 +13,7 @@ type RouteOptions struct {
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark FwMark `json:"default_mark,omitempty"`
DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"`
DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"`
DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"`

View File

@@ -145,8 +145,8 @@ type RawRouteOptionsActionOptions struct {
OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"override_port,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"`

View File

@@ -194,8 +194,9 @@ func (r LogicalHeadlessRule) IsValid() bool {
}
type _PlainRuleSetCompat struct {
Version uint8 `json:"version"`
Options PlainRuleSet `json:"-"`
Version uint8 `json:"version"`
Options PlainRuleSet `json:"-"`
RawMessage json.RawMessage `json:"-"`
}
type PlainRuleSetCompat _PlainRuleSetCompat
@@ -229,6 +230,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
if err != nil {
return err
}
r.RawMessage = bytes
return nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/udpnat2"
@@ -80,34 +81,22 @@ func (i *Inbound) Close() error {
}
func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
var destination M.Socksaddr
switch i.overrideOption {
case 1:
destination = i.overrideDestination
case 2:
destination = i.overrideDestination
destination.Port = i.listener.UDPAddr().Port
case 3:
destination = source
destination.Port = i.overrideDestination.Port
}
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil)
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil)
}
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = i.Tag()
metadata.InboundType = i.Type()
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
destination := metadata.OriginDestination
switch i.overrideOption {
case 1:
metadata.Destination = i.overrideDestination
destination = i.overrideDestination
case 2:
destination := i.overrideDestination
destination.Port = metadata.Destination.Port
metadata.Destination = destination
destination.Addr = i.overrideDestination.Addr
case 3:
metadata.Destination.Port = i.overrideDestination.Port
destination.Port = metadata.Destination.Port
}
metadata.Destination = destination
if i.overrideOption != 0 {
i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
}
@@ -116,7 +105,6 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
i.logger.InfoContext(ctx, "inbound packet connection from ", source)
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
var metadata adapter.InboundContext
metadata.Inbound = i.Tag()
metadata.InboundType = i.Type()
@@ -125,8 +113,21 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
//nolint:staticcheck
metadata.InboundOptions = i.listener.ListenOptions().InboundOptions
metadata.Source = source
destination = i.listener.UDPAddr()
switch i.overrideOption {
case 1:
destination = i.overrideDestination
case 2:
destination.Addr = i.overrideDestination.Addr
case 3:
destination.Port = i.overrideDestination.Port
default:
}
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
metadata.Destination = destination
metadata.OriginDestination = i.listener.UDPAddr()
if i.overrideOption != 0 {
conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination)
}
i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -32,16 +32,12 @@ var (
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer
domainStrategy dns.DomainStrategy
fallbackDelay time.Duration
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
overrideOption int
overrideDestination M.Socksaddr
logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer
domainStrategy dns.DomainStrategy
fallbackDelay time.Duration
overrideOption int
overrideDestination M.Socksaddr
// loopBack *loopBackDetector
}
@@ -52,15 +48,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
logger: logger,
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
networkStrategy: C.NetworkStrategy(options.NetworkStrategy),
networkType: common.Map(options.NetworkType, option.InterfaceType.Build),
fallbackNetworkType: common.Map(options.FallbackNetworkType, option.InterfaceType.Build),
networkFallbackDelay: time.Duration(options.NetworkFallbackDelay),
dialer: outboundDialer,
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
logger: logger,
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer,
// loopBack: newLoopBackDetector(router),
}
//nolint:staticcheck
@@ -178,10 +170,10 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.networkType, h.fallbackNetworkType, h.fallbackDelay)
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay)
}
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
@@ -221,7 +213,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
}
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination

View File

@@ -82,16 +82,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err = http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
err := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

View File

@@ -67,7 +67,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if err != nil {
return nil, E.New("invalid down speed format: ", options.Down)
return nil, E.Cause(err, "invalid down speed format: ", options.Down)
}
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps

View File

@@ -4,6 +4,7 @@ import (
"context"
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
@@ -70,6 +71,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
Logger: logger,
BrutalDebug: options.BrutalDebug,
ServerAddress: options.ServerOptions.Build(),
ServerPorts: options.ServerPorts,
HopInterval: time.Duration(options.HopInterval),
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
SalamanderPassword: salamanderPassword,

View File

@@ -110,11 +110,19 @@ func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketC
metadata.InboundType = h.Type()
user, loaded := auth.UserFromContext[string](ctx)
if !loaded {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "inbound packet connection")
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
}
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
return
}
metadata.User = user
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
}
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -61,7 +61,7 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
logger: logger,
}
var err error
inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex))
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}

View File

@@ -92,11 +92,19 @@ func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketC
metadata.InboundType = h.Type()
user, loaded := auth.UserFromContext[string](ctx)
if !loaded {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "inbound packet connection")
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
}
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
return
}
metadata.User = user
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
}
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -159,16 +159,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil && h.transport == nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

View File

@@ -209,6 +209,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
platformInterface: service.FromContext[platform.Interface](ctx),
platformOptions: common.PtrValueOrDefault(options.Platform),
}
for _, routeAddressSet := range options.RouteAddressSet {
ruleSet, loaded := router.RuleSet(routeAddressSet)
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 {
ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
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 {
if !options.AutoRoute {
return nil, E.New("`auto_route` is required by `auto_redirect`")
@@ -229,32 +245,11 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil {
return nil, E.Cause(err, "initialize auto-redirect")
}
if runtime.GOOS != "android" {
var markMode bool
for _, routeAddressSet := range options.RouteAddressSet {
ruleSet, loaded := router.RuleSet(routeAddressSet)
if !loaded {
return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
}
ruleSet.IncRef()
inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
markMode = true
}
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
if !loaded {
return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
}
ruleSet.IncRef()
inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
markMode = true
}
if markMode {
inbound.tunOptions.AutoRedirectMarkMode = true
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
if err != nil {
return nil, err
}
if !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) {
inbound.tunOptions.AutoRedirectMarkMode = true
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
if err != nil {
return nil, err
}
}
}
@@ -310,18 +305,66 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if t.tunOptions.Name == "" {
t.tunOptions.Name = tun.CalculateInterfaceName("")
}
if t.platformInterface == nil {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeRuleSet := range t.routeRuleSet {
ipSets := routeRuleSet.ExtractIPSet()
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
}
routeRuleSet.DecRef()
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
if t.autoRedirect != nil {
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
}
}
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
ipSets := routeExcludeRuleSet.ExtractIPSet()
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
}
routeExcludeRuleSet.DecRef()
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
if t.autoRedirect != nil {
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
}
}
}
var (
tunInterface tun.Tun
err error
)
monitor := taskmonitor.New(t.logger, C.StartTimeout)
monitor.Start("open tun interface")
tunOptions := t.tunOptions
if t.autoRedirect == nil && !(runtime.GOOS == "android" && t.platformInterface != nil) {
for _, ipSet := range t.routeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)
} else {
tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix)
}
}
}
for _, ipSet := range t.routeExcludeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix)
} else {
tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix)
}
}
}
}
monitor.Start("open interface")
if t.platformInterface != nil {
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions)
} else {
tunInterface, err = tun.New(t.tunOptions)
tunInterface, err = tun.New(tunOptions)
}
monitor.Finish()
t.tunOptions.Name = tunOptions.Name
if err != nil {
return E.Cause(err, "configure tun interface")
}
@@ -366,39 +409,15 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
return E.Cause(err, "starting TUN interface")
}
if t.autoRedirect != nil {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeRuleSet := range t.routeRuleSet {
ipSets := routeRuleSet.ExtractIPSet()
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
}
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
}
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
ipSets := routeExcludeRuleSet.ExtractIPSet()
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
}
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
}
monitor.Start("initialize auto-redirect")
err := t.autoRedirect.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "auto-redirect")
}
for _, routeRuleSet := range t.routeRuleSet {
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeRuleSet.DecRef()
}
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeExcludeRuleSet.DecRef()
}
t.routeAddressSet = nil
t.routeExcludeAddressSet = nil
}
t.routeAddressSet = nil
t.routeExcludeAddressSet = nil
}
return nil
}

View File

@@ -139,16 +139,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil && h.transport == nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

Some files were not shown because too many files have changed in this diff Show More