Compare commits

..

73 Commits

Author SHA1 Message Date
世界 9ee56ae7f5 Bump version 2026-05-03 09:07:20 +08:00
macronut 48c65a2f8a Add more spoof method
Signed-off-by: macronut <4027187+macronut@users.noreply.github.com>
2026-05-02 23:07:54 +08:00
世界 772ab58f8c Update naiveproxy to v148.0.7778.96-1 2026-05-02 19:18:41 +08:00
世界 bb60b58f00 Allow customizing TUN DNS mode and hijack interface DNS by default 2026-05-02 19:18:41 +08:00
世界 85889fc701 cronet: Remove additional QUIC certificate check 2026-05-02 19:18:41 +08:00
世界 98b21227fa dns: Add mDNS server 2026-05-02 19:18:41 +08:00
世界 fdec2fe051 dns: Add preferred_by rule item 2026-05-02 19:18:41 +08:00
世界 e171852b19 dns: Add neighbor-based hostname resolution to local server 2026-05-02 19:18:41 +08:00
世界 2a533b0daa Fix tailscale start dependencies 2026-04-28 18:35:00 +08:00
世界 ddc20355cf sing: Fix contentjson crash 2026-04-28 18:34:51 +08:00
世界 abedea4e57 Bump version 2026-04-28 15:12:39 +08:00
世界 1bad8d87eb dns: Add timeout configuration 2026-04-28 15:12:39 +08:00
世界 60a5f874e2 Cleanup compatible code for legacy Go 2026-04-28 15:12:38 +08:00
世界 610aa9432d ssh: Add cipher, MAC, and key exchange configuration 2026-04-28 15:12:38 +08:00
世界 78f502d016 tun: Add read waiter support for gVisor conn 2026-04-28 08:55:06 +08:00
世界 b418ee1a98 Improve oom-killer 2026-04-28 08:55:06 +08:00
世界 fd1624e426 Preserve comments between formatting 2026-04-28 08:55:06 +08:00
世界 d9c9edd6f8 tun: Add compatibility with docker bridge 2026-04-28 08:55:06 +08:00
nekohasekai 1610c18a2f Add Windows TLS engine 2026-04-28 08:55:05 +08:00
世界 3d3c8a2fe9 Improve UDP batch support 2026-04-28 08:20:44 +08:00
世界 968bbd832c Fix stderr deprecated manager 2026-04-28 08:20:44 +08:00
世界 d06d3bf2a4 Fix darwin cgo DNS again 2026-04-28 08:04:55 +08:00
世界 fbc1ca36e6 platform: Improve oom-killer 2026-04-28 08:04:54 +08:00
世界 0f719a0715 Fix ACME HTTP-01 challenge for IPv6 literal addresses 2026-04-28 08:04:54 +08:00
世界 817f9c6364 Add ACME profile support for IP address certificates 2026-04-28 08:04:54 +08:00
世界 5cd421401d Fix goroutine leak in networkquality tool
Serialize probe rounds in startProber to eliminate unbounded fan-out of
fire-and-forget probe goroutines (up to 100/sec per direction), and close
HTTP/3 transports via transport.Close() in addition to CloseIdleConnections.
2026-04-28 08:04:54 +08:00
世界 acfb90a38e Fix Tailscale search domain response name mismatch 2026-04-28 08:04:54 +08:00
世界 6b51bd6778 Log DNS optimistic background refresh outcomes 2026-04-28 08:04:54 +08:00
世界 b358fdd564 Add search domain support for Tailscale DNS 2026-04-28 08:04:53 +08:00
世界 6ecd3deef5 Fix tls-spoof 2026-04-28 08:04:53 +08:00
世界 a8b8f15a76 Fix Apple TLS metadata capture 2026-04-28 08:04:53 +08:00
世界 3c9c4aee07 Strip EDNS padding from upstream DNS responses 2026-04-28 08:04:53 +08:00
世界 32f56cce07 Defer implicit default HTTP client fallback to first use 2026-04-28 08:04:53 +08:00
世界 4b15fccb78 Scope HTTP/2 fallback and HTTP/3 broken state per authority 2026-04-28 08:04:52 +08:00
世界 9bffb64bbd Fix macOS tlsspoof 2026-04-28 08:04:52 +08:00
世界 4a68bc9274 Reject IP literal server name with TLS spoof 2026-04-28 08:04:52 +08:00
世界 fee6afdbf5 Fix use-after-free of pooled value buffers in bbolt Batch writes 2026-04-28 08:04:52 +08:00
世界 90a642ef96 Reject pure-IP rule-set references without match_response
DNS rules referencing rule-sets that contain only ip_cidr predicates
silently stopped matching when legacy DNS mode was disabled, because the
IP-CIDR branch cannot match against an in-flight DNS query. The existing
validation intentionally let every rule_set through on the premise that
mixed sets still work via their non-IP branches, which is only true when
such a branch exists. Track whether a rule-set carries any non-IP-CIDR
predicate and reject pure-IP references the same way bare ip_cidr fields
are already rejected.
2026-04-28 08:04:52 +08:00
世界 9bee53225c Fix legacy rule-set download_detour blocked by empty direct check 2026-04-28 08:04:52 +08:00
世界 08ce083b9b Add TLS spoof support 2026-04-28 08:04:51 +08:00
世界 6ef38042f3 Standardize hosts path 2026-04-28 08:04:51 +08:00
世界 5a618c6b68 Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines 2026-04-28 08:04:39 +08:00
世界 0d7230e42c oom-killer: Record report before reset network 2026-04-28 08:04:34 +08:00
世界 0319b22c76 Add optimistic DNS cache 2026-04-28 08:04:34 +08:00
世界 e6f5c2438b Fix tailscale error 2026-04-28 08:04:34 +08:00
世界 8fb019164e Fix darwin cgo DNS again 2026-04-28 08:04:33 +08:00
世界 104c7ae24d Fix stun test 2026-04-28 08:04:33 +08:00
世界 3828b4f30e documentation: Fix missing update for ip_version and query_type 2026-04-28 08:04:33 +08:00
世界 08260fa770 Add cloudflared inbound 2026-04-28 08:04:33 +08:00
世界 5779b46ca6 Fix lint errors 2026-04-28 08:04:33 +08:00
世界 012db0ef0e platform: Wrap command RPC error returns with E.Cause 2026-04-28 08:04:32 +08:00
世界 684ba79113 Add package_name_regex route, DNS and headless rule item 2026-04-28 08:04:32 +08:00
世界 e714efa792 documentation: Fixes 2026-04-28 08:04:32 +08:00
世界 e75e1c98a9 Un-deprecate ip_accept_any DNS rule item 2026-04-28 08:04:32 +08:00
世界 524578a635 tools: Tailscale status 2026-04-28 08:04:32 +08:00
世界 eade67726a Fix darwin local DNS transport 2026-04-28 08:04:31 +08:00
世界 ec75e5ec0a Fix rules lock 2026-04-28 08:04:31 +08:00
世界 a7b02f9cb2 Revert "Also enable certificate store by default on Apple platforms"
This reverts commit 62cb06c02f.
2026-04-28 08:04:31 +08:00
世界 ce6d6838a8 tools: Tailscale status 2026-04-28 08:04:31 +08:00
世界 ccc2742d2b platform: Fix darwin signal handler 2026-04-28 08:04:31 +08:00
世界 0b8f380817 tools: Network Quality & STUN 2026-04-28 08:04:31 +08:00
世界 5ee373c549 oom-killer: Free memory on pressure notification and use gradual interval backoff 2026-04-28 08:04:30 +08:00
世界 0f6d110dad Fix deprecated warning double-formatting on localized clients 2026-04-28 08:04:30 +08:00
世界 9e8f13c0ac platform: Fix set local 2026-04-28 08:04:30 +08:00
nekohasekai e1a7ab3df3 Add evaluate DNS rule action and related rule items 2026-04-28 08:04:30 +08:00
世界 ff94634b52 Also enable certificate store by default on Apple platforms
`SecTrustEvaluateWithError` is serial
2026-04-28 08:04:29 +08:00
世界 26ddb928d9 platform: Add OOM Report & Crash Report 2026-04-28 08:04:29 +08:00
世界 0155352ff1 Add BBR profile and hop interval randomization for Hysteria2 2026-04-28 08:04:29 +08:00
nekohasekai 83fa58f60a Refactor ACME support to certificate provider 2026-04-28 08:04:29 +08:00
世界 4f6d0ffafc documentation: Update descriptions for neighbor rules 2026-04-28 08:04:29 +08:00
世界 1c02d7e8b0 Add macOS support for MAC and hostname rule items 2026-04-28 08:04:28 +08:00
世界 c57e86427c Add Android support for MAC and hostname rule items 2026-04-28 08:04:28 +08:00
世界 d3575cc8c2 Add MAC and hostname rule items 2026-04-28 08:04:28 +08:00
642 changed files with 10153 additions and 80619 deletions
+1 -1
View File
@@ -1 +1 @@
617d38f41f935b46a68f550d9add2e38abb3f168
cd9fcba9981bf69c1aab541887d2758d84dc31f3
-2
View File
@@ -31,8 +31,6 @@ body:
- sing-box for macOS Graphical Client
- sing-box for Apple tvOS Graphical Client
- sing-box for Android Graphical Client
- sing-box for Windows Graphical Client
- sing-box for Linux Graphical Client
- Third-party graphical clients that advertise themselves as using sing-box (Windows)
- Third-party graphical clients that advertise themselves as using sing-box (Android)
- Others
-2
View File
@@ -31,8 +31,6 @@ body:
- sing-box for macOS 图形客户端程序
- sing-box for Apple tvOS 图形客户端程序
- sing-box for Android 图形客户端程序
- sing-box for Windows 图形客户端程序
- sing-box for Linux 图形客户端程序
- 宣传使用 sing-box 的第三方图形客户端程序 (Windows)
- 宣传使用 sing-box 的第三方图形客户端程序 (Android)
- 其他
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail
VERSION="1.25.12"
VERSION="1.25.9"
PATCH_COMMITS=(
"afe69d3cec1c6dcf0f1797b20546795730850070"
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
+7 -7
View File
@@ -2,14 +2,14 @@
set -euo pipefail
VERSION="1.25.12"
VERSION="1.25.9"
PATCH_COMMITS=(
"da4094da73b3b419e3f347594d805e2831f65667"
"824aa60e77f06dbae86c20a164c78df722eb7047"
"a3b6ba31c8cc67b6d899b978bba7b53e95afc46b"
"edfa8de63435a409a59f60731b66ab5940d6d3a4"
"284f9b24d6284984966a8431e30fdc2583938f96"
"9864798dee8dd47b55d1d5100d2f1b909a2a6e6c"
"466f6c7a29bc098b0d4c987b803c779222894a11"
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
"a90777dcf692dd2168577853ba743b4338721b06"
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
)
CURL_ARGS=(
-fL
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.12
go-version: ~1.25.9
- name: Clone cronet-go
if: matrix.naive
run: |
+2 -41
View File
@@ -18,60 +18,21 @@ on:
- testing
- unstable
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
cancel-in-progress: true
jobs:
build:
name: Lint ${{ matrix.goos }}/${{ matrix.goarch }}
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: windows
goarch: amd64
- goos: windows
goarch: '386'
- goos: windows
goarch: arm64
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: linux
goarch: arm
- goos: linux
goarch: '386'
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: android
goarch: arm64
# - goos: freebsd
# goarch: amd64
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
with:
version: latest
args: --timeout=30m
+5 -3
View File
@@ -29,7 +29,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.12
go-version: ~1.25.9
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -45,7 +45,7 @@ jobs:
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
@@ -72,7 +72,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.12
go-version: ~1.25.9
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -182,6 +182,8 @@ jobs:
-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 }}
-3
View File
@@ -4,6 +4,3 @@
[submodule "clients/android"]
path = clients/android
url = https://github.com/SagerNet/sing-box-for-android.git
[submodule "clients/desktop"]
path = clients/desktop
url = https://github.com/SagerNet/sing-box-for-desktop.git
+18 -10
View File
@@ -1,6 +1,6 @@
version: "2"
run:
go: "1.24"
go: "1.25"
build-tags:
- with_gvisor
- with_quic
@@ -17,28 +17,29 @@ run:
linters:
default: none
enable:
- govet
- ineffassign
- staticcheck
- unused
- modernize
settings:
modernize:
disable:
- omitzero # nested struct omitempty -> omitzero changes JSON output semantics
staticcheck:
checks:
- all
- -QF1008 # could remove embedded field "<interface>" from selector
- -ST1003 # should not use ALL_CAPS in Go names; use CamelCase instead
- -QF1001 # could apply De Morgan's law
- -S1000
- -S1008
- -S1017
- -ST1003
- -QF1001
- -QF1003
- -QF1008
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- transport/simple-obfs
- \.pb\.go$
- third_party$
- builtin$
- examples$
@@ -53,3 +54,10 @@ formatters:
- prefix(github.com/sagernet/)
- default
custom-order: true
exclusions:
generated: lax
paths:
- transport/simple-obfs
- third_party$
- builtin$
- examples$
+24 -42
View File
@@ -14,7 +14,7 @@ PREFIX ?= $(shell go env GOPATH)
SING_FFI ?= sing-ffi
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
.PHONY: test release docs build schema
.PHONY: test release docs build
build:
export GOTOOLCHAIN=local && \
@@ -32,24 +32,27 @@ ci_build:
generate_completions:
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
schema:
go run -ldflags "$(LDFLAGS_SHARED)" --tags "$(TAGS)" $(MAIN) schema -o docs/schema.json
install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
fmt:
@golangci-lint fmt
@gofumpt -l -w .
@gofmt -s -w .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_docs:
go run ./cmd/internal/format_docs
fmt_install:
go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@latest
lint:
GOOS=linux golangci-lint run ./...
GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./...
# GOOS=freebsd golangci-lint run ./...
# GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
@@ -88,9 +91,6 @@ release_install:
update_android_version:
go run ./cmd/internal/update_android_version
update_desktop_version:
go run ./cmd/internal/update_desktop_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./gradlew --stop
@@ -98,13 +98,10 @@ upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
VERSION_CODE=$$(grep VERSION_CODE ../sing-box-for-android/version.properties | cut -d= -f2); \
VERSION_NAME=$$(grep VERSION_NAME ../sing-box-for-android/version.properties | cut -d= -f2); \
printf '{\n "version_code": %s,\n "version_name": "%s"\n}\n' "$$VERSION_CODE" "$$VERSION_NAME" > dist/release_android/SFA-version-metadata.json
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
rm -rf dist/release_android
release_android: build_android upload_android
release_android: lib_android update_android_version build_android upload_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
@@ -114,27 +111,29 @@ publish_android:
build_ios:
cd ../sing-box-for-apple && \
rm -rf build/SFI.xcarchive && \
xcodebuild clean -scheme SFI -derivedDataPath build/SFI.dd && \
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -derivedDataPath build/SFI.dd -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
xcodebuild clean -scheme SFI && \
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
upload_ios_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
build_ios_deb:
$(MAKE) -C ../sing-box-for-apple build_ios_deb
export_ios_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
cp build/SFI/sing-box.ipa dist/SFI.ipa
upload_ios_deb:
ghr --replace --draft --prerelease "v${VERSION}" ../sing-box-for-apple/build/jailbreak/"SFI-${VERSION}-iphoneos-arm64.deb"
upload_ios_ipa:
cd dist && \
cp SFI.ipa "SFI-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
release_ios: build_ios upload_ios_app_store
release_ios_deb: build_ios_deb upload_ios_deb
build_macos:
cd ../sing-box-for-apple && \
rm -rf build/SFM.xcarchive && \
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -derivedDataPath build/SFM.dd -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
upload_macos_app_store:
cd ../sing-box-for-apple && \
@@ -175,35 +174,18 @@ upload_macos_pkg:
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
replace_macos_pkg:
mkdir -p dist/SFM
cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg"
cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg"
cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
upload_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
replace_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
replace_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -derivedDataPath build/SFT.dd -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
upload_tvos_app_store:
cd ../sing-box-for-apple && \
@@ -268,8 +250,8 @@ lib_apple_new:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.13
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.13
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
docs:
venv/bin/mkdocs serve
-8
View File
@@ -2,8 +2,6 @@ package certificate
import (
"context"
"maps"
"slices"
"sync"
"github.com/sagernet/sing-box/adapter"
@@ -46,12 +44,6 @@ func NewRegistry() *Registry {
}
}
func (m *Registry) OptionTypes() []string {
m.access.Lock()
defer m.access.Unlock()
return slices.Sorted(maps.Keys(m.optionsType))
}
func (m *Registry) CreateOptions(providerType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
+5 -8
View File
@@ -18,7 +18,6 @@ import (
type DNSRouter interface {
Lifecycle
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
ExchangeAsync(ctx context.Context, message *dns.Msg, options DNSQueryOptions, callback func(response *dns.Msg, err error))
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
ClearCache()
LookupReverseMapping(ip netip.Addr) (string, bool)
@@ -28,7 +27,6 @@ type DNSRouter interface {
type DNSClient interface {
Start()
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error)
ExchangeAsync(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool, callback func(response *dns.Msg, err error))
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error)
ClearCache()
}
@@ -44,16 +42,16 @@ type DNSQueryOptions struct {
ClientSubnet netip.Prefix
}
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (DNSQueryOptions, error) {
if options == nil || options.Server == "" {
return DNSQueryOptions{}, nil
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
if options == nil {
return &DNSQueryOptions{}, nil
}
transportManager := service.FromContext[DNSTransportManager](ctx)
transport, loaded := transportManager.Transport(options.Server)
if !loaded {
return DNSQueryOptions{}, E.New("domain resolver not found: " + options.Server)
return nil, E.New("domain resolver not found: " + options.Server)
}
return DNSQueryOptions{
return &DNSQueryOptions{
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
@@ -86,7 +84,6 @@ type DNSTransport interface {
// Exchanges that are currently using those connections may fail.
Reset()
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
ExchangeAsync(ctx context.Context, message *dns.Msg, callback func(response *dns.Msg, err error))
}
type DNSTransportWithPreferredDomain interface {
-8
View File
@@ -2,8 +2,6 @@ package endpoint
import (
"context"
"maps"
"slices"
"sync"
"github.com/sagernet/sing-box/adapter"
@@ -46,12 +44,6 @@ func NewRegistry() *Registry {
}
}
func (m *Registry) OptionTypes() []string {
m.access.Lock()
defer m.access.Unlock()
return slices.Sorted(maps.Keys(m.optionsType))
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
+11 -4
View File
@@ -13,10 +13,11 @@ import (
type ClashServer interface {
LifecycleService
ConnectionTracker
Mode() string
ModeList() []string
SetMode(mode string)
AddModeUpdateHook(hook *observable.Subscriber[struct{}])
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
HistoryStorage() URLTestHistoryStorage
}
type URLTestHistory struct {
@@ -24,6 +25,14 @@ type URLTestHistory struct {
Delay uint16 `json:"delay"`
}
type URLTestHistoryStorage interface {
SetHook(hook *observable.Subscriber[struct{}])
LoadURLTestHistory(tag string) *URLTestHistory
DeleteURLTestHistory(tag string)
StoreURLTestHistory(tag string, history *URLTestHistory)
Close() error
}
type V2RayServer interface {
LifecycleService
StatsService() ConnectionTracker
@@ -32,8 +41,6 @@ type V2RayServer interface {
type CacheFile interface {
LifecycleService
CacheID() string
StoreFakeIP() bool
FakeIPStorage
-2
View File
@@ -86,7 +86,6 @@ type InboundContext struct {
DestinationAddresses []netip.Addr
DNSResponse *dns.Msg
NamedDNSResponses map[string]*dns.Msg
DestinationAddressMatchFromResponse bool
SourceGeoIPCode string
GeoIPCode string
@@ -95,7 +94,6 @@ type InboundContext struct {
SourceHostname string
QueryType uint16
FakeIP bool
PreMatch bool
// rule cache
-8
View File
@@ -2,8 +2,6 @@ package inbound
import (
"context"
"maps"
"slices"
"sync"
"github.com/sagernet/sing-box/adapter"
@@ -46,12 +44,6 @@ func NewRegistry() *Registry {
}
}
func (m *Registry) OptionTypes() []string {
m.access.Lock()
defer m.access.Unlock()
return slices.Sorted(maps.Keys(m.optionsType))
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
-5
View File
@@ -1,5 +0,0 @@
package adapter
type NetworkNamespaceManager interface {
ResolvePath(nameOrPath string) string
}
-101
View File
@@ -1,101 +0,0 @@
package adapter
import (
"net/netip"
"time"
)
const (
OpenConnectStateConnecting = "connecting"
OpenConnectStateAuthPending = "auth-pending"
OpenConnectStateConnected = "connected"
OpenConnectStateError = "error"
)
type OpenConnectEndpoint interface {
Endpoint
OpenConnectStatus() OpenConnectStatus
StatusUpdated() <-chan struct{}
CompleteAuthChallenge(challengeID string, response OpenConnectAuthResponse) error
CancelAuthChallenge(challengeID string) error
}
type OpenConnectStatus struct {
State string
AuthChallenge *OpenConnectAuthChallenge
Error string
TunnelInfo *OpenConnectTunnelInfo
}
type OpenConnectTunnelInfo struct {
Server string
Flavor string
Transport string
IPv4 []netip.Prefix
IPv6 []netip.Prefix
DNS []netip.Addr
MTU uint32
ConnectedSince time.Time
}
type OpenConnectAuthChallenge struct {
ID string
Banner string
Message string
Error string
Form *OpenConnectAuthForm
Browser *OpenConnectBrowserRequest
}
type OpenConnectAuthForm struct {
Fields []OpenConnectAuthFormField
}
type OpenConnectBrowserRequest struct {
URL string
FinalURL string
CookieNames []string
EarlyCookieNames []string
HeaderNames []string
CallbackURLPrefixes []string
CacheID string
}
type OpenConnectBrowserCookie struct {
Name string
Value string
}
type OpenConnectBrowserHeader struct {
Name string
Values []string
}
type OpenConnectAuthResponse struct {
Form *OpenConnectAuthFormResponse
Browser *OpenConnectBrowserResult
}
type OpenConnectAuthFormResponse struct {
Values map[string]string
}
type OpenConnectBrowserResult struct {
FinalURL string
Cookies []OpenConnectBrowserCookie
Headers []OpenConnectBrowserHeader
}
type OpenConnectAuthFormField struct {
SubmissionKey string
Name string
Label string
Kind string
Value string
Options []OpenConnectAuthFormChoice
}
type OpenConnectAuthFormChoice struct {
Value string
Label string
}
-57
View File
@@ -1,57 +0,0 @@
package adapter
import (
"net/netip"
"time"
)
const (
OpenVPNStateConnecting = "connecting"
OpenVPNStateAuthPending = "auth-pending"
OpenVPNStateConnected = "connected"
OpenVPNStateError = "error"
)
type OpenVPNEndpoint interface {
Endpoint
OpenVPNStatus() OpenVPNStatus
StatusUpdated() <-chan struct{}
CompleteChallenge(challengeID string, response OpenVPNChallengeResponse) error
CancelChallenge(challengeID string) error
}
type OpenVPNStatus struct {
State string
Challenge *OpenVPNChallenge
Error string
TunnelInfo *OpenVPNTunnelInfo
}
type OpenVPNTunnelInfo struct {
Server string
Network string
Cipher string
IPv4 []netip.Prefix
IPv6 []netip.Prefix
DNS []netip.Addr
MTU uint32
ConnectedSince time.Time
}
type OpenVPNChallenge struct {
ID string
Kind string
Username string
Message string
URL string
SecretMessage string
Echo bool
PreviousError string
Deadline time.Time
}
type OpenVPNChallengeResponse struct {
Username string
Password string
Secret string
}
+5 -10
View File
@@ -3,6 +3,7 @@ package adapter
import (
"context"
"net/netip"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -22,19 +23,13 @@ type Outbound interface {
type OutboundWithPreferredRoutes interface {
Outbound
PreferredDomain(metadata *InboundContext, domain string) bool
PreferredAddress(metadata *InboundContext, address netip.Addr) bool
PreferredDomain(domain string) bool
PreferredAddress(address netip.Addr) bool
}
type OutboundWithMultiplex interface {
type DirectRouteOutbound interface {
Outbound
MultiplexEnabled() bool
}
type FlowOutbound interface {
Outbound
tun.Port
PreMatchFlow(network string, destination netip.Addr) PreMatchAction
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
}
type OutboundRegistry interface {
-8
View File
@@ -2,8 +2,6 @@ package outbound
import (
"context"
"maps"
"slices"
"sync"
"github.com/sagernet/sing-box/adapter"
@@ -46,12 +44,6 @@ func NewRegistry() *Registry {
}
}
func (r *Registry) OptionTypes() []string {
r.access.Lock()
defer r.access.Unlock()
return slices.Sorted(maps.Keys(r.optionsType))
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
+1 -39
View File
@@ -16,7 +16,6 @@ type PlatformInterface interface {
UsePlatformInterface() bool
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
ProcessPlatformOptions(options option.TunPlatformOptions) error
UsePlatformDefaultInterfaceMonitor() bool
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
@@ -30,6 +29,7 @@ type PlatformInterface interface {
ClearDNSCache()
RequestPermissionForWIFIState() error
ReadWIFIState() WIFIState
SystemCertificates() []string
UsePlatformConnectionOwnerFinder() bool
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
@@ -44,44 +44,6 @@ type PlatformInterface interface {
UsePlatformNeighborResolver() bool
StartNeighborMonitor(listener NeighborUpdateListener) error
CloseNeighborMonitor(listener NeighborUpdateListener) error
UsePlatformShell() bool
CheckPlatformShell() error
OpenShellSession(user *PlatformUser, command string, env []string, term string, rows int32, cols int32) (ShellSession, error)
LookupUser(username string) (*PlatformUser, error)
LookupSFTPServer() (string, error)
ReadSystemSSHHostKey() ([]byte, error)
TailscaleHostname() string
UsePlatformBridge() bool
CreateBridge(options BridgeOptions) (BridgeSession, error)
}
type BridgeOptions struct {
BridgeName string
MTU uint32
Inet4Port netip.Addr
Inet6Port netip.Addr
Interface string
RuleIndex int
RouteTable int
}
type BridgeSession interface {
FileDescriptor() int
Name() string
Inet6Active() bool
SetEgress(interfaceName string) error
Close() error
}
type PlatformUser struct {
Username string
Uid int
Gid int
HomeDir string
Shell string
Groups []int
}
type FindConnectionOwnerRequest struct {
+2 -77
View File
@@ -3,12 +3,9 @@ package adapter
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
@@ -18,8 +15,7 @@ import (
type Router interface {
Lifecycle
ConnectionRouter
PreMatch(metadata InboundContext, firstPacket []byte) PreMatchResult
HijackDNSPacket(ctx context.Context, payload []byte, writer N.PacketWriter, metadata InboundContext)
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool)
Rules() []Rule
@@ -30,81 +26,9 @@ type Router interface {
ResetNetwork()
}
type PreMatchAction uint8
const (
PreMatchContinue PreMatchAction = iota
PreMatchFlow
PreMatchReject
PreMatchDrop
PreMatchBypass
PreMatchHijackDNS
)
type PreMatchResult struct {
Action PreMatchAction
Outbound Outbound
Destination netip.AddrPort
UDPTimeout time.Duration
NewTracker func() tun.FlowTracker
}
func JudgeFlow(router Router, inbound string, inboundType string, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
var networkName string
switch network {
case uint8(header.TCPProtocolNumber):
networkName = N.NetworkTCP
case uint8(header.UDPProtocolNumber):
networkName = N.NetworkUDP
case uint8(header.ICMPv4ProtocolNumber), uint8(header.ICMPv6ProtocolNumber):
networkName = N.NetworkICMP
default:
return tun.FlowVerdict{Action: tun.ActionAccept}
}
metadata := InboundContext{
Inbound: inbound,
InboundType: inboundType,
Network: networkName,
Source: M.SocksaddrFromNetIP(source),
Destination: M.SocksaddrFromNetIP(destination),
}
if networkName == N.NetworkICMP {
metadata.Source.Port = 0
metadata.Destination.Port = 0
}
result := router.PreMatch(metadata, firstPacket)
switch result.Action {
case PreMatchFlow:
port, isPort := result.Outbound.(tun.Port)
if !isPort {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
verdict := tun.FlowVerdict{Action: tun.ActionFlow, Port: port, UDPTimeout: result.UDPTimeout, NewTracker: result.NewTracker}
if result.Destination.IsValid() {
destinationPort := result.Destination.Port()
if networkName == N.NetworkICMP {
destinationPort = destination.Port()
}
verdict.Destination = netip.AddrPortFrom(result.Destination.Addr(), destinationPort)
}
return verdict
case PreMatchReject:
return tun.FlowVerdict{Action: tun.ActionReject}
case PreMatchDrop:
return tun.FlowVerdict{Action: tun.ActionDrop}
case PreMatchBypass:
return tun.FlowVerdict{Action: tun.ActionBypass}
case PreMatchHijackDNS:
return tun.FlowVerdict{Action: tun.ActionHijackDNS}
default:
return tun.FlowVerdict{Action: tun.ActionAccept}
}
}
type ConnectionTracker interface {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
RoutedFlow(ctx context.Context, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) tun.FlowTracker
}
// Deprecated: Use ConnectionRouterEx instead.
@@ -122,6 +46,7 @@ type ConnectionRouterEx interface {
type RuleSet interface {
Name() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error
PostStart() error
Metadata() RuleSetMetadata
ExtractIPSet() []*netipx.IPSet
IncRef()
-4
View File
@@ -23,10 +23,6 @@ type DNSRule interface {
LegacyPreMatch(metadata *InboundContext) bool
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
MatchResponseTag() string
MatchResponseTags() []string
MatchResponseAnonymous() bool
Race() bool
}
type RuleAction interface {
-19
View File
@@ -1,19 +0,0 @@
package adapter
import (
"context"
"github.com/sagernet/sing/service"
)
type SecurityPolicy interface {
CheckFeature(ctx context.Context, feature string) error
}
func CheckSecurityFeature(ctx context.Context, feature string) error {
policy := service.FromContext[SecurityPolicy](ctx)
if policy == nil {
return nil
}
return policy.CheckFeature(ctx, feature)
}
-8
View File
@@ -2,8 +2,6 @@ package service
import (
"context"
"maps"
"slices"
"sync"
"github.com/sagernet/sing-box/adapter"
@@ -46,12 +44,6 @@ func NewRegistry() *Registry {
}
}
func (m *Registry) OptionTypes() []string {
m.access.Lock()
defer m.access.Unlock()
return slices.Sorted(maps.Keys(m.optionsType))
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
-17
View File
@@ -5,8 +5,6 @@ import "context"
type TailscaleEndpoint interface {
SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error
StartTailscalePing(ctx context.Context, peerIP string, fn func(*TailscalePingResult)) error
SetTailscaleExitNode(ctx context.Context, stableID string) error
Logout(ctx context.Context) error
}
type TailscalePingResult struct {
@@ -24,9 +22,7 @@ type TailscaleEndpointStatus struct {
NetworkName string
MagicDNSSuffix string
Self *TailscalePeer
ExitNode *TailscalePeer
UserGroups []*TailscaleUserGroup
KeyAuth bool
}
type TailscaleUserGroup struct {
@@ -38,29 +34,16 @@ type TailscaleUserGroup struct {
}
type TailscalePeer struct {
StableID string
HostName string
DNSName string
OS string
TailscaleIPs []string
SSHHostKeys []string
Online bool
ExitNode bool
ExitNodeOption bool
ShareeNode bool
Expired bool
Active bool
RxBytes int64
TxBytes int64
UserID int64
KeyExpiry int64
LastSeen int64
}
type ShellSession interface {
MasterFD() int32
Resize(rows int32, cols int32) error
Signal(signal int32) error
WaitExit() (int32, error)
Close() error
}
-15
View File
@@ -1,15 +0,0 @@
//go:build with_usbip && (linux || (darwin && cgo) || windows)
package adapter
import (
"context"
"github.com/sagernet/sing-usbip"
)
type USBIPDynamicServer interface {
AddDevice(info usbip.ProvidedDeviceInfo, transport usbip.DeviceTransport) (string, error)
RemoveDevice(busID string)
SubscribeDevices(ctx context.Context, listener func([]usbip.ControlDeviceInfo))
}
-7
View File
@@ -1,7 +0,0 @@
//go:build !with_usbip || !(linux || (darwin && cgo) || windows)
package adapter
type USBIPDynamicServer interface {
usbipNotIncluded()
}
+5 -49
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"net/http"
"os"
"runtime/debug"
"time"
@@ -18,11 +17,8 @@ import (
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/httpclient"
"github.com/sagernet/sing-box/common/netns"
"github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/trafficcontrol"
"github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/experimental"
@@ -44,8 +40,6 @@ var _ adapter.SimpleLifecycle = (*Box)(nil)
type Box struct {
createdAt time.Time
debugOptions option.DebugOptions
debugHTTPServer *http.Server
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
@@ -65,9 +59,8 @@ type Box struct {
type Options struct {
option.Options
Context context.Context
PlatformLogWriter log.PlatformWriter
NetworkNamespaceHolderArgs []string
Context context.Context
PlatformLogWriter log.PlatformWriter
}
func Context(
@@ -145,8 +138,7 @@ func New(options Options) (*Box, error) {
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug)
err := checkDebugOptions(debugOptions)
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
if err != nil {
return nil, err
}
@@ -162,12 +154,6 @@ func New(options Options) (*Box, error) {
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
needAPIService := common.Any(options.Services, func(it option.Service) bool {
return it.Type == C.TypeAPI
})
if service.PtrFromContext[urltest.HistoryStorage](ctx) == nil {
ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage())
}
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
var defaultLogWriter io.Writer
if platformInterface != nil {
@@ -176,7 +162,7 @@ func New(options Options) (*Box, error) {
logFactory, err := log.New(log.Options{
Context: ctx,
Options: common.PtrValueOrDefault(options.Log),
Observable: needClashAPI || needAPIService,
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformLogWriter,
@@ -184,7 +170,6 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "create log factory")
}
service.MustRegister[log.Factory](ctx, logFactory)
var internalServices []adapter.LifecycleService
routeOptions := common.PtrValueOrDefault(options.Route)
@@ -200,12 +185,6 @@ func New(options Options) (*Box, error) {
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
internalServices = append(internalServices, certificateStore)
}
netnsManager, err := netns.NewManager(logFactory.NewLogger("netns"), options.NetworkNamespaces, options.NetworkNamespaceHolderArgs)
if err != nil {
return nil, err
}
service.MustRegister[adapter.NetworkNamespaceManager](ctx, netnsManager)
internalServices = append(internalServices, netnsManager)
dnsOptions := common.PtrValueOrDefault(options.DNS)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
@@ -242,12 +221,6 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "initialize router")
}
if needClashAPI || needAPIService {
trafficManager := trafficcontrol.NewManager(outboundManager)
service.MustRegisterPtr(ctx, trafficManager)
router.AppendTracker(trafficManager)
internalServices = append(internalServices, trafficManager)
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
@@ -425,6 +398,7 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "create clash-server")
}
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
internalServices = append(internalServices, clashServer)
}
@@ -440,12 +414,6 @@ func New(options Options) (*Box, error) {
}
}
if ntpOptions.Enabled {
if ntpOptions.WriteToSystem {
err = adapter.CheckSecurityFeature(ctx, "NTP `write_to_system`")
if err != nil {
return nil, err
}
}
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
if err != nil {
return nil, E.Cause(err, "create NTP service")
@@ -474,7 +442,6 @@ func New(options Options) (*Box, error) {
router: router,
httpClientService: httpClientService,
createdAt: createdAt,
debugOptions: debugOptions,
logFactory: logFactory,
logger: logFactory.Logger(),
internalService: internalServices,
@@ -528,11 +495,6 @@ func (s *Box) preStart() error {
if err != nil {
return E.Cause(err, "start logger")
}
applyDebugOptions(s.debugOptions)
s.debugHTTPServer, err = startDebugHTTPServer(s.debugOptions)
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
if err != nil {
return err
@@ -604,12 +566,6 @@ func (s *Box) Close() error {
close(s.done)
}
var err error
if s.debugHTTPServer != nil {
err = E.Append(err, s.debugHTTPServer.Close(), func(err error) error {
return E.Cause(err, "close debug HTTP server")
})
s.debugHTTPServer = nil
}
for _, closeItem := range []struct {
name string
service adapter.Lifecycle
Submodule clients/desktop deleted from cebee0d527
+1 -1
View File
@@ -289,7 +289,7 @@ func prepareAppStore(ctx context.Context) error {
return err
}
if len(builds.Data) == 0 {
log.Fatal(string(platform), " ", tag, " no build found")
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {
-119
View File
@@ -1,119 +0,0 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
)
var (
debugEnabled bool
outputPath string
target string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&outputPath, "output", "", "output path")
flag.StringVar(&target, "target", runtime.GOOS+"/"+runtime.GOARCH, "target platform")
}
func main() {
flag.Parse()
err := build()
if err != nil {
log.Fatal(err)
}
}
func build() error {
targetParts := strings.Split(target, "/")
if len(targetParts) != 2 || targetParts[0] == "" || targetParts[1] == "" {
return E.New("invalid target: ", target)
}
operatingSystem := targetParts[0]
architecture := targetParts[1]
if outputPath == "" {
outputPath = "sing-box-daemon"
if operatingSystem == "windows" {
outputPath += ".exe"
}
}
absoluteOutputPath, err := filepath.Abs(outputPath)
if err != nil {
return E.Cause(err, "resolve output path")
}
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
if err != nil {
return E.Cause(err, "create output directory")
}
version, err := build_shared.ReadTag()
if err != nil {
return E.Cause(err, "read version")
}
cgoEnabled := operatingSystem != "windows" && os.Getenv("CC") != ""
tags, err := buildTags(operatingSystem, architecture, cgoEnabled)
if err != nil {
return err
}
arguments := []string{
"build",
"-v",
"-trimpath",
"-buildvcs=false",
"-tags", strings.Join(tags, ","),
"-ldflags", build_shared.LinkerFlags(version, debugEnabled),
"-o", absoluteOutputPath,
}
if operatingSystem == "windows" && architecture == "386" {
arguments = append(arguments, "-gcflags=net=-l")
}
arguments = append(arguments, "./experimental/boxdd")
command := exec.Command("go", arguments...)
cgoEnabledValue := "0"
if cgoEnabled {
cgoEnabledValue = "1"
}
command.Env = append(os.Environ(),
"CGO_ENABLED="+cgoEnabledValue,
"GOOS="+operatingSystem,
"GOARCH="+architecture,
"GOTOOLCHAIN=local",
)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err = command.Run()
if err != nil {
return E.Cause(err, "build sing-box daemon")
}
return nil
}
func buildTags(operatingSystem string, architecture string, cgoEnabled bool) ([]string, error) {
tagsFile := "release/DEFAULT_BUILD_TAGS"
if operatingSystem == "windows" {
if architecture == "386" {
tagsFile = "release/DEFAULT_BUILD_TAGS_OTHERS"
} else {
tagsFile = "release/DEFAULT_BUILD_TAGS_WINDOWS"
}
} else if !cgoEnabled {
tagsFile = "release/DEFAULT_BUILD_TAGS_OTHERS"
}
content, err := os.ReadFile(tagsFile)
if err != nil {
return nil, E.Cause(err, "read build tags")
}
tags := strings.Split(strings.TrimSpace(string(content)), ",")
if debugEnabled {
tags = append(tags, "debug")
}
return tags, nil
}
+5 -5
View File
@@ -60,10 +60,10 @@ func init() {
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-ldflags", build_shared.LinkerFlags(currentTag, false))
debugFlags = append(debugFlags, "-ldflags", build_shared.LinkerFlags(currentTag, true))
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_usbip", "with_openvpn", "with_openconnect", "badlinkname", "tfogo_checklinkname0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
// memcTags = append(memcTags, "with_tailscale")
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
@@ -163,14 +163,14 @@ func buildAndroid() {
bindTarget := getAndroidBindTarget()
// Build main variant (SDK 24)
// Build main variant (SDK 23)
mainTags := append([]string{}, sharedTags...)
// mainTags = append(mainTags, memcTags...)
if debugEnabled {
mainTags = append(mainTags, debugTags...)
}
buildAndroidVariant(AndroidBuildConfig{
AndroidAPI: 24,
AndroidAPI: 23,
OutputName: "libbox.aar",
Tags: mainTags,
}, bindTarget)
-15
View File
@@ -1,15 +0,0 @@
package build_shared
import "strings"
func LinkerFlags(version string, debug bool) string {
flags := []string{
"-X github.com/sagernet/sing-box/constant.Version=" + version,
"-X runtime.godebugDefault=multipathtcp=0,tlssha1=1,tlsunsafeekm=1",
"-checklinkname=0",
}
if !debug {
flags = append(flags, "-s", "-w", "-buildid=")
}
return strings.Join(flags, " ")
}
-165
View File
@@ -1,165 +0,0 @@
package main
import (
"archive/zip"
"crypto/sha256"
"flag"
"io"
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
)
var outputPath string
func init() {
flag.StringVar(&outputPath, "output", "", "output AAR path")
}
func main() {
flag.Parse()
err := merge()
if err != nil {
log.Fatal(err)
}
}
func merge() error {
inputPaths := flag.Args()
if outputPath == "" {
return E.New("missing output path")
}
if len(inputPaths) == 0 {
return E.New("missing input AAR paths")
}
archiveReaders := make([]*zip.ReadCloser, 0, len(inputPaths))
for _, inputPath := range inputPaths {
archiveReader, err := zip.OpenReader(inputPath)
if err != nil {
return E.Cause(err, "open input AAR: ", inputPath)
}
archiveReaders = append(archiveReaders, archiveReader)
}
defer func() {
for _, archiveReader := range archiveReaders {
archiveReader.Close()
}
}()
referenceEntries := make(map[string][sha256.Size]byte)
selectedEntries := make([]*zip.File, 0)
selectedJNIEntries := make(map[string]bool)
for inputIndex, archiveReader := range archiveReaders {
seenEntries := make(map[string]bool)
for _, archiveFile := range archiveReader.File {
if strings.HasPrefix(archiveFile.Name, "jni/") {
if archiveFile.FileInfo().IsDir() {
continue
}
if selectedJNIEntries[archiveFile.Name] {
return E.New("duplicate AAR JNI entry: ", archiveFile.Name)
}
selectedJNIEntries[archiveFile.Name] = true
selectedEntries = append(selectedEntries, archiveFile)
continue
}
entryDigest, err := digestEntry(archiveFile)
if err != nil {
return E.Cause(err, "read AAR entry: ", archiveFile.Name)
}
if inputIndex == 0 {
referenceEntries[archiveFile.Name] = entryDigest
selectedEntries = append(selectedEntries, archiveFile)
} else {
referenceDigest, loaded := referenceEntries[archiveFile.Name]
if !loaded {
return E.New("unexpected AAR entry: ", archiveFile.Name)
}
if referenceDigest != entryDigest {
return E.New("AAR entry differs between architectures: ", archiveFile.Name)
}
}
seenEntries[archiveFile.Name] = true
}
if inputIndex > 0 {
for referenceName := range referenceEntries {
if !seenEntries[referenceName] {
return E.New("missing AAR entry: ", referenceName)
}
}
}
}
absoluteOutputPath, err := filepath.Abs(outputPath)
if err != nil {
return E.Cause(err, "resolve output AAR path")
}
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
if err != nil {
return E.Cause(err, "create output AAR directory")
}
temporaryFile, err := os.CreateTemp(filepath.Dir(absoluteOutputPath), ".merge-aar-*.aar")
if err != nil {
return E.Cause(err, "create temporary output AAR")
}
temporaryPath := temporaryFile.Name()
defer os.Remove(temporaryPath)
archiveWriter := zip.NewWriter(temporaryFile)
for _, archiveFile := range selectedEntries {
rawReader, openErr := archiveFile.OpenRaw()
if openErr != nil {
archiveWriter.Close()
temporaryFile.Close()
return E.Cause(openErr, "open raw AAR entry: ", archiveFile.Name)
}
header := archiveFile.FileHeader
rawWriter, createErr := archiveWriter.CreateRaw(&header)
if createErr != nil {
archiveWriter.Close()
temporaryFile.Close()
return E.Cause(createErr, "create output AAR entry: ", archiveFile.Name)
}
_, copyErr := io.Copy(rawWriter, rawReader)
if copyErr != nil {
archiveWriter.Close()
temporaryFile.Close()
return E.Cause(copyErr, "copy output AAR entry: ", archiveFile.Name)
}
}
err = archiveWriter.Close()
if err != nil {
temporaryFile.Close()
return E.Cause(err, "finalize output AAR")
}
err = temporaryFile.Close()
if err != nil {
return E.Cause(err, "close output AAR")
}
err = os.Rename(temporaryPath, absoluteOutputPath)
if err != nil {
return E.Cause(err, "replace output AAR")
}
return nil
}
func digestEntry(archiveFile *zip.File) ([sha256.Size]byte, error) {
entryReader, err := archiveFile.Open()
if err != nil {
return [sha256.Size]byte{}, err
}
digest := sha256.New()
_, err = io.Copy(digest, entryReader)
closeErr := entryReader.Close()
if err != nil {
return [sha256.Size]byte{}, err
}
if closeErr != nil {
return [sha256.Size]byte{}, closeErr
}
var result [sha256.Size]byte
copy(result[:], digest.Sum(nil))
return result, nil
}
@@ -1,172 +0,0 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"howett.net/plist"
)
type xcFrameworkInfo struct {
AvailableLibraries []xcFrameworkLibrary `plist:"AvailableLibraries"`
}
type xcFrameworkLibrary struct {
BinaryPath string `plist:"BinaryPath"`
LibraryIdentifier string `plist:"LibraryIdentifier"`
LibraryPath string `plist:"LibraryPath"`
SupportedArchitectures []string `plist:"SupportedArchitectures"`
SupportedPlatform string `plist:"SupportedPlatform"`
SupportedPlatformVariant string `plist:"SupportedPlatformVariant"`
}
type frameworkSlice struct {
rootPath string
library xcFrameworkLibrary
}
var outputPath string
func init() {
flag.StringVar(&outputPath, "output", "", "output XCFramework path")
}
func main() {
flag.Parse()
err := merge()
if err != nil {
log.Fatal(err)
}
}
func merge() error {
inputPaths := flag.Args()
if outputPath == "" {
return E.New("missing output path")
}
if len(inputPaths) == 0 {
return E.New("missing input XCFramework paths")
}
frameworkGroups := make(map[string][]frameworkSlice)
for _, inputPath := range inputPaths {
infoFile, err := os.Open(filepath.Join(inputPath, "Info.plist"))
if err != nil {
return E.Cause(err, "open XCFramework metadata: ", inputPath)
}
var info xcFrameworkInfo
decoder := plist.NewDecoder(infoFile)
err = decoder.Decode(&info)
closeErr := infoFile.Close()
if err != nil {
return E.Cause(err, "decode XCFramework metadata: ", inputPath)
}
if closeErr != nil {
return E.Cause(closeErr, "close XCFramework metadata: ", inputPath)
}
for _, library := range info.AvailableLibraries {
groupName := library.SupportedPlatform + "|" + library.SupportedPlatformVariant
frameworkGroups[groupName] = append(frameworkGroups[groupName], frameworkSlice{
rootPath: inputPath,
library: library,
})
}
}
groupNames := make([]string, 0, len(frameworkGroups))
for groupName := range frameworkGroups {
groupNames = append(groupNames, groupName)
}
sort.Strings(groupNames)
absoluteOutputPath, err := filepath.Abs(outputPath)
if err != nil {
return E.Cause(err, "resolve output XCFramework path")
}
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
if err != nil {
return E.Cause(err, "create output XCFramework directory")
}
temporaryDirectory, err := os.MkdirTemp(filepath.Dir(absoluteOutputPath), ".merge-xcframework-*")
if err != nil {
return E.Cause(err, "create XCFramework merge directory")
}
defer os.RemoveAll(temporaryDirectory)
frameworkPaths := make([]string, 0, len(groupNames))
for groupIndex, groupName := range groupNames {
frameworkSlices := frameworkGroups[groupName]
firstSlice := frameworkSlices[0]
firstFrameworkPath := filepath.Join(firstSlice.rootPath, firstSlice.library.LibraryIdentifier, firstSlice.library.LibraryPath)
if len(frameworkSlices) == 1 {
frameworkPaths = append(frameworkPaths, firstFrameworkPath)
continue
}
architectures := make(map[string]bool)
binaryPaths := make([]string, 0, len(frameworkSlices))
for _, currentSlice := range frameworkSlices {
if currentSlice.library.LibraryPath != firstSlice.library.LibraryPath || currentSlice.library.BinaryPath != firstSlice.library.BinaryPath {
return E.New("incompatible XCFramework slices for platform: ", currentSlice.library.SupportedPlatform)
}
for _, architecture := range currentSlice.library.SupportedArchitectures {
if architectures[architecture] {
return E.New("duplicate XCFramework architecture: ", architecture)
}
architectures[architecture] = true
}
binaryPaths = append(binaryPaths, filepath.Join(currentSlice.rootPath, currentSlice.library.LibraryIdentifier, currentSlice.library.BinaryPath))
}
mergedFrameworkPath := filepath.Join(temporaryDirectory, "framework-"+strconv.Itoa(groupIndex), filepath.Base(firstSlice.library.LibraryPath))
copyCommand := exec.Command("ditto", firstFrameworkPath, mergedFrameworkPath)
copyCommand.Stdout = os.Stdout
copyCommand.Stderr = os.Stderr
err = copyCommand.Run()
if err != nil {
return E.Cause(err, "copy XCFramework slice")
}
binaryRelativePath, relativeErr := filepath.Rel(firstSlice.library.LibraryPath, firstSlice.library.BinaryPath)
if relativeErr != nil {
return E.Cause(relativeErr, "resolve XCFramework binary path")
}
if binaryRelativePath == "." || strings.HasPrefix(binaryRelativePath, ".."+string(filepath.Separator)) {
return E.New("invalid XCFramework binary path: ", firstSlice.library.BinaryPath)
}
mergedBinaryPath := filepath.Join(mergedFrameworkPath, binaryRelativePath)
temporaryBinaryPath := mergedBinaryPath + ".merged"
lipoArguments := append([]string{"lipo", "-create"}, binaryPaths...)
lipoArguments = append(lipoArguments, "-output", temporaryBinaryPath)
lipoCommand := exec.Command("xcrun", lipoArguments...)
lipoCommand.Stdout = os.Stdout
lipoCommand.Stderr = os.Stderr
err = lipoCommand.Run()
if err != nil {
return E.Cause(err, "merge XCFramework binaries")
}
err = os.Rename(temporaryBinaryPath, mergedBinaryPath)
if err != nil {
return E.Cause(err, "replace merged XCFramework binary")
}
frameworkPaths = append(frameworkPaths, mergedFrameworkPath)
}
err = os.RemoveAll(absoluteOutputPath)
if err != nil {
return E.Cause(err, "remove output XCFramework")
}
xcodebuildArguments := []string{"-create-xcframework"}
for _, frameworkPath := range frameworkPaths {
xcodebuildArguments = append(xcodebuildArguments, "-framework", frameworkPath)
}
xcodebuildArguments = append(xcodebuildArguments, "-output", absoluteOutputPath)
xcodebuildCommand := exec.Command("xcodebuild", xcodebuildArguments...)
xcodebuildCommand.Stdout = os.Stdout
xcodebuildCommand.Stderr = os.Stderr
err = xcodebuildCommand.Run()
if err != nil {
return E.Cause(err, "create XCFramework")
}
return nil
}
+2 -2
View File
@@ -48,8 +48,8 @@ func GetRuntimeEnv(key string) (string, error) {
if readErr != nil {
return "", readErr
}
envStrings := strings.SplitSeq(string(data), "\n")
for envItem := range envStrings {
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
+1 -1
View File
@@ -39,7 +39,7 @@ func main() {
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties"))
var propsList [][]string
for propLine := range strings.SplitSeq(string(localProps), "\n") {
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
var (
+1 -40
View File
@@ -106,7 +106,6 @@ func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent stri
}
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
globalSettings := collectBuildSettings(objectsMap)
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
@@ -117,51 +116,13 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
if bundleIDObject == nil {
continue
}
bundleID := expandBuildVariables(bundleIDObject.(string), globalSettings)
if common.Contains(bundleIDList, bundleID) {
if common.Contains(bundleIDList, bundleIDObject.(string)) {
objectKeyList = append(objectKeyList, objectKey)
}
}
return objectKeyList
}
func collectBuildSettings(objectsMap map[string]any) map[string]string {
settings := make(map[string]string)
for _, object := range objectsMap {
buildSettings, loaded := object.(map[string]any)["buildSettings"].(map[string]any)
if !loaded {
continue
}
for key, value := range buildSettings {
valueString, isString := value.(string)
if !isString {
continue
}
settings[key] = valueString
}
}
return settings
}
var buildVariableRegexp = regexp.MustCompile(`\$[({]([A-Za-z0-9_]+)[)}]`)
func expandBuildVariables(value string, settings map[string]string) string {
for {
expanded := buildVariableRegexp.ReplaceAllStringFunc(value, func(match string) string {
name := buildVariableRegexp.FindStringSubmatch(match)[1]
replacement, loaded := settings[name]
if !loaded {
return match
}
return replacement
})
if expanded == value {
return expanded
}
value = expanded
}
}
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {
@@ -1,66 +0,0 @@
package main
import (
"encoding/json"
"flag"
"os"
"path/filepath"
"runtime"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
)
var (
flagRunInCI bool
flagRunNightly bool
)
type versionMetadata struct {
Version string `json:"version"`
GoVersion string `json:"go_version"`
}
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTag())
desktopPath := "../sing-box-for-desktop"
if flagRunInCI {
desktopPath = "clients/desktop"
}
desktopPath = common.Must1(filepath.Abs(desktopPath))
versionPath := filepath.Join(desktopPath, "version.json")
versionFile := common.Must1(os.Open(versionPath))
var metadata versionMetadata
common.Must(json.NewDecoder(versionFile).Decode(&metadata))
common.Must(versionFile.Close())
newGoVersion := runtime.Version()
versionUpdated := metadata.Version != newVersion
goVersionUpdated := metadata.GoVersion != newGoVersion
if !(versionUpdated || goVersionUpdated) {
log.Info("version not changed")
return
}
if versionUpdated {
log.Info("updated version from ", metadata.Version, " to ", newVersion)
}
if goVersionUpdated {
log.Info("updated Go version from ", metadata.GoVersion, " to ", newGoVersion)
}
if flagRunInCI && !flagRunNightly {
log.Fatal("version changed, commit changes first.")
}
metadata.Version = newVersion
metadata.GoVersion = newGoVersion
outputFile := common.Must1(os.Create(versionPath))
encoder := json.NewEncoder(outputFile)
encoder.SetIndent("", " ")
common.Must(encoder.Encode(metadata))
common.Must(outputFile.Close())
}
+3 -4
View File
@@ -61,17 +61,16 @@ func geoipExport(countryCode string) error {
outputFile *os.File
outputWriter io.Writer
)
switch flagGeoipExportOutput {
case "stdout":
if flagGeoipExportOutput == "stdout" {
outputWriter = os.Stdout
case flagGeoipExportDefaultOutput:
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
default:
} else {
outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil {
return err
+3 -4
View File
@@ -43,17 +43,16 @@ func geositeExport(category string) error {
outputFile *os.File
outputWriter io.Writer
)
switch commandGeositeExportOutput {
case "stdout":
if commandGeositeExportOutput == "stdout" {
outputWriter = os.Stdout
case commandGeositeExportDefaultOutput:
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
default:
} else {
outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil {
return err
-20
View File
@@ -1,20 +0,0 @@
package main
import (
"github.com/sagernet/sing-box/common/netns"
"github.com/spf13/cobra"
)
var commandNetnsHolder = &cobra.Command{
Use: "netns-holder",
Args: cobra.NoArgs,
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
netns.Hold()
},
}
func init() {
mainCommand.AddCommand(commandNetnsHolder)
}
+11 -31
View File
@@ -104,17 +104,10 @@ func readConfigAndMerge() (option.Options, error) {
if err != nil {
return option.Options{}, err
}
return mergeOptionsList(optionsList)
}
func mergeOptionsList(optionsList []*OptionsEntry) (option.Options, error) {
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var (
mergedMessage json.RawMessage
err error
)
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
@@ -129,7 +122,11 @@ func mergeOptionsList(optionsList []*OptionsEntry) (option.Options, error) {
return mergedOptions, nil
}
func create(options option.Options) (*box.Box, context.CancelFunc, error) {
func create() (*box.Box, context.CancelFunc, error) {
options, err := readConfigAndMerge()
if err != nil {
return nil, nil, err
}
if disableColor {
if options.Log == nil {
options.Log = &option.LogOptions{}
@@ -138,9 +135,8 @@ func create(options option.Options) (*box.Box, context.CancelFunc, error) {
}
ctx, cancel := context.WithCancel(globalCtx)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
NetworkNamespaceHolderArgs: []string{"/proc/self/exe", commandNetnsHolder.Use},
Context: ctx,
Options: options,
})
if err != nil {
cancel()
@@ -171,25 +167,13 @@ func create(options option.Options) (*box.Box, context.CancelFunc, error) {
}
func run() error {
optionsList, err := readConfig()
if err != nil {
return err
}
options, err := mergeOptionsList(optionsList)
if err != nil {
return err
}
err = runInUserNamespaceIfNeeded(options, optionsList)
if err != nil {
return err
}
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(osSignals)
for {
instance, cancel, createErr := create(options)
if createErr != nil {
return createErr
instance, cancel, err := create()
if err != nil {
return err
}
runtimeDebug.FreeOSMemory()
for {
@@ -214,10 +198,6 @@ func run() error {
}
break
}
options, err = readConfigAndMerge()
if err != nil {
return err
}
}
}
-78
View File
@@ -1,78 +0,0 @@
package main
import (
"bytes"
"os"
"os/exec"
"os/signal"
"syscall"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/sys/unix"
)
func runInUserNamespaceIfNeeded(options option.Options, optionsList []*OptionsEntry) error {
if !common.Any(options.NetworkNamespaces, func(namespace option.NetworkNamespace) bool {
return namespace.Type == C.NetNsTypeUnshare
}) {
return nil
}
var header unix.CapUserHeader
header.Version = unix.LINUX_CAPABILITY_VERSION_3
var data [2]unix.CapUserData
err := unix.Capget(&header, &data[0])
if err != nil {
return E.Cause(err, "get capabilities")
}
if data[0].Effective&(1<<unix.CAP_SYS_ADMIN) != 0 {
return nil
}
command := exec.Command("/proc/self/exe", os.Args[1:]...)
command.Args = os.Args
command.Stdin = os.Stdin
for _, entry := range optionsList {
if entry.path == "stdin" {
command.Stdin = bytes.NewReader(entry.content)
}
}
command.Stdout = os.Stdout
command.Stderr = os.Stderr
command.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{ContainerID: os.Geteuid(), HostID: os.Geteuid(), Size: 1},
},
GidMappings: []syscall.SysProcIDMap{
{ContainerID: os.Getegid(), HostID: os.Getegid(), Size: 1},
},
GidMappingsEnableSetgroups: false,
AmbientCaps: []uintptr{unix.CAP_SYS_ADMIN, unix.CAP_NET_ADMIN, unix.CAP_NET_RAW},
Setpgid: true,
Pdeathsig: syscall.SIGKILL,
}
err = command.Start()
if err != nil {
return E.Cause(err, "create user namespace for unshare network namespace (is unprivileged user namespace creation allowed by the kernel?)")
}
signalChannel := make(chan os.Signal, 4)
signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
go func() {
for receivedSignal := range signalChannel {
command.Process.Signal(receivedSignal)
}
}()
err = command.Wait()
exitError, isExitError := E.Cast[*exec.ExitError](err)
if isExitError {
os.Exit(exitError.ExitCode())
}
if err != nil {
return E.Cause(err, "wait user namespace child")
}
os.Exit(0)
return nil
}
-9
View File
@@ -1,9 +0,0 @@
//go:build !linux
package main
import "github.com/sagernet/sing-box/option"
func runInUserNamespaceIfNeeded(options option.Options, optionsList []*OptionsEntry) error {
return nil
}
-45
View File
@@ -1,45 +0,0 @@
package main
import (
"context"
"os"
"reflect"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/schema"
"github.com/spf13/cobra"
)
var commandSchemaFlagOutput string
var commandSchema = &cobra.Command{
Use: "schema",
Short: "Generate configuration JSON schema",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateSchema()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandSchema.Flags().StringVarP(&commandSchemaFlagOutput, "output", "o", "", "write schema to file instead of stdout")
mainCommand.AddCommand(commandSchema)
}
func generateSchema() error {
content, err := schema.Generate(include.Context(context.Background()), reflect.TypeFor[option.Options]())
if err != nil {
return err
}
if commandSchemaFlagOutput != "" {
return os.WriteFile(commandSchemaFlagOutput, content, 0o644)
}
_, err = os.Stdout.Write(content)
return err
}
+3 -1
View File
@@ -112,7 +112,9 @@ func IsValid(versionName string) bool {
}
func Parse(versionName string) (version Version) {
versionName = strings.TrimPrefix(versionName, "v")
if strings.HasPrefix(versionName, "v") {
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-")
versionName = parts[0]
+14 -13
View File
@@ -16,13 +16,12 @@ import (
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service/filemanager"
"github.com/sagernet/sing/service"
)
var _ adapter.CertificateStore = (*Store)(nil)
type Store struct {
ctx context.Context
access sync.RWMutex
storeType string
systemPool *x509.CertPool
@@ -31,8 +30,7 @@ type Store struct {
certificatePaths []string
certificateDirectoryPaths []string
watcher *fswatch.Watcher
//nolint:unused // populated only on darwin && cgo via the storePlatform embed.
platform storePlatform
platform storePlatform
}
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
@@ -44,10 +42,14 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
switch storeType {
case C.CertificateStoreSystem:
systemPool = x509.NewCertPool()
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
var systemValid bool
for _, certificate := range systemCertificates() {
systemPool.AddCert(certificate)
systemValid = true
if platformInterface != nil {
for _, cert := range platformInterface.SystemCertificates() {
if systemPool.AppendCertsFromPEM([]byte(cert)) {
systemValid = true
}
}
}
if !systemValid {
certPool, err := x509.SystemCertPool()
@@ -62,7 +64,6 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
return nil, E.New("unknown certificate store: ", options.Store)
}
store := &Store{
ctx: ctx,
storeType: storeType,
systemPool: systemPool,
certificate: strings.Join(options.Certificate, "\n"),
@@ -169,7 +170,7 @@ func (s *Store) update() error {
appendPEMBlock(pemBuffer, s.certificate)
}
for _, path := range s.certificatePaths {
pemContent, err := filemanager.ReadFile(s.ctx, path)
pemContent, err := os.ReadFile(path)
if err != nil {
return err
}
@@ -180,7 +181,7 @@ func (s *Store) update() error {
}
var firstErr error
for _, directoryPath := range s.certificateDirectoryPaths {
directoryEntries, err := readUniqueDirectoryEntries(s.ctx, directoryPath)
directoryEntries, err := readUniqueDirectoryEntries(directoryPath)
if err != nil {
if firstErr == nil && !os.IsNotExist(err) {
firstErr = E.Cause(err, "invalid certificate directory: ", directoryPath)
@@ -188,7 +189,7 @@ func (s *Store) update() error {
continue
}
for _, directoryEntry := range directoryEntries {
pemContent, err := filemanager.ReadFile(s.ctx, filepath.Join(directoryPath, directoryEntry.Name()))
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
if err == nil && currentPool.AppendCertsFromPEM(pemContent) {
appendPEMBlock(pemBuffer, string(pemContent))
}
@@ -227,8 +228,8 @@ func (s *Store) newBasePool() (*x509.CertPool, error) {
}
}
func readUniqueDirectoryEntries(ctx context.Context, dir string) ([]fs.DirEntry, error) {
files, err := filemanager.ReadDir(ctx, dir)
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
-1
View File
@@ -2,7 +2,6 @@
package certificate
//nolint:unused // referenced by Store.platform; populated only in store_darwin.go.
type storePlatform struct{}
func (s *Store) updatePlatformLocked(_ []byte) error {
-91
View File
@@ -1,91 +0,0 @@
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
void *box_system_certificates_der(uintptr_t vmPtr, int *out_length) {
*out_length = 0;
JavaVM *vm = (JavaVM *) vmPtr;
JNIEnv *env = NULL;
int attached = 0;
jint getEnvResult = (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6);
if (getEnvResult == JNI_EDETACHED) {
if ((*vm)->AttachCurrentThread(vm, &env, NULL) != JNI_OK) {
return NULL;
}
attached = 1;
} else if (getEnvResult != JNI_OK) {
return NULL;
}
unsigned char *result = NULL;
int resultLength = 0;
jclass keyStoreClass = (*env)->FindClass(env, "java/security/KeyStore");
jmethodID getInstance = (*env)->GetStaticMethodID(env, keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;");
jstring storeName = (*env)->NewStringUTF(env, "AndroidCAStore");
jobject keyStore = (*env)->CallStaticObjectMethod(env, keyStoreClass, getInstance, storeName);
if ((*env)->ExceptionCheck(env) || keyStore == NULL) {
goto done;
}
jmethodID load = (*env)->GetMethodID(env, keyStoreClass, "load", "(Ljava/io/InputStream;[C)V");
(*env)->CallVoidMethod(env, keyStore, load, NULL, NULL);
if ((*env)->ExceptionCheck(env)) {
goto done;
}
jmethodID aliasesMethod = (*env)->GetMethodID(env, keyStoreClass, "aliases", "()Ljava/util/Enumeration;");
jmethodID getCertificate = (*env)->GetMethodID(env, keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;");
jobject aliases = (*env)->CallObjectMethod(env, keyStore, aliasesMethod);
if ((*env)->ExceptionCheck(env) || aliases == NULL) {
goto done;
}
jclass enumerationClass = (*env)->FindClass(env, "java/util/Enumeration");
jmethodID hasMoreElements = (*env)->GetMethodID(env, enumerationClass, "hasMoreElements", "()Z");
jmethodID nextElement = (*env)->GetMethodID(env, enumerationClass, "nextElement", "()Ljava/lang/Object;");
jclass certificateClass = (*env)->FindClass(env, "java/security/cert/Certificate");
jmethodID getEncoded = (*env)->GetMethodID(env, certificateClass, "getEncoded", "()[B");
while ((*env)->CallBooleanMethod(env, aliases, hasMoreElements)) {
jstring alias = (jstring) (*env)->CallObjectMethod(env, aliases, nextElement);
jobject certificate = (*env)->CallObjectMethod(env, keyStore, getCertificate, alias);
(*env)->DeleteLocalRef(env, alias);
if ((*env)->ExceptionCheck(env) || certificate == NULL) {
(*env)->ExceptionClear(env);
continue;
}
jbyteArray encoded = (jbyteArray) (*env)->CallObjectMethod(env, certificate, getEncoded);
(*env)->DeleteLocalRef(env, certificate);
if ((*env)->ExceptionCheck(env) || encoded == NULL) {
(*env)->ExceptionClear(env);
continue;
}
jsize encodedLength = (*env)->GetArrayLength(env, encoded);
unsigned char *grown = realloc(result, resultLength + encodedLength);
if (grown == NULL) {
(*env)->DeleteLocalRef(env, encoded);
free(result);
result = NULL;
resultLength = 0;
goto done;
}
result = grown;
(*env)->GetByteArrayRegion(env, encoded, 0, encodedLength, (jbyte *) (result + resultLength));
resultLength += encodedLength;
(*env)->DeleteLocalRef(env, encoded);
}
done:
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionClear(env);
}
if (attached) {
(*vm)->DetachCurrentThread(vm);
}
*out_length = resultLength;
return result;
}
-34
View File
@@ -1,34 +0,0 @@
//go:build android
package certificate
/*
#include <stdint.h>
#include <stdlib.h>
extern void *box_system_certificates_der(uintptr_t vm, int *out_length);
*/
import "C"
import (
"crypto/x509"
"github.com/sagernet/sing-box/common/jni"
)
func systemCertificates() []*x509.Certificate {
vm := jni.VM()
if vm == 0 {
return nil
}
var length C.int
pointer := C.box_system_certificates_der(C.uintptr_t(vm), &length)
if pointer == nil {
return nil
}
defer C.free(pointer)
certificates, err := x509.ParseCertificates(C.GoBytes(pointer, length))
if err != nil {
return nil
}
return certificates
}
-9
View File
@@ -1,9 +0,0 @@
//go:build !android || !cgo
package certificate
import "crypto/x509"
func systemCertificates() []*x509.Certificate {
return nil
}
+11 -7
View File
@@ -63,7 +63,9 @@ parseLine:
}
continue
}
ruleLine = strings.TrimSuffix(ruleLine, "|")
if strings.HasSuffix(ruleLine, "|") {
ruleLine = ruleLine[:len(ruleLine)-1]
}
var (
isExclude bool
isSuffix bool
@@ -74,7 +76,7 @@ parseLine:
)
if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
params := common.SubstringAfter(ruleLine, "$")
for param := range strings.SplitSeq(params, ",") {
for _, param := range strings.Split(params, ",") {
paramParts := strings.Split(param, "=")
var ignored bool
if len(paramParts) > 0 && len(paramParts) <= 2 {
@@ -104,7 +106,9 @@ parseLine:
ruleLine = ruleLine[2:]
isExclude = true
}
ruleLine = strings.TrimSuffix(ruleLine, "|")
if strings.HasSuffix(ruleLine, "|") {
ruleLine = ruleLine[:len(ruleLine)-1]
}
if strings.HasPrefix(ruleLine, "||") {
ruleLine = ruleLine[2:]
isSuffix = true
@@ -410,18 +414,18 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
}
func parseAdGuardHostLine(ruleLine string) (string, error) {
before, after, ok := strings.Cut(ruleLine, " ")
if !ok {
idx := strings.Index(ruleLine, " ")
if idx == -1 {
return "", os.ErrInvalid
}
address, err := netip.ParseAddr(before)
address, err := netip.ParseAddr(ruleLine[:idx])
if err != nil {
return "", err
}
if !address.IsUnspecified() {
return "", nil
}
domain := after
domain := ruleLine[idx+1:]
if !M.IsDomainName(domain) {
return "", E.New("invalid domain name: ", domain)
}
+13 -41
View File
@@ -24,7 +24,7 @@ import (
var (
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
_ UDPListener = (*DefaultDialer)(nil)
_ WireGuardListener = (*DefaultDialer)(nil)
)
type DefaultDialer struct {
@@ -36,7 +36,6 @@ type DefaultDialer struct {
udpAddr4 string
udpAddr6 string
netns string
autoDetectBindFunc control.Func
connectionManager adapter.ConnectionManager
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
@@ -61,7 +60,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
autoDetectBindFunc control.Func
)
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
@@ -97,7 +95,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
if platformInterface != nil && platformInterface.UsePlatformNetworkInterfaces() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
@@ -121,7 +119,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
autoDetectBindFunc = bindFunc
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
@@ -155,13 +152,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if options.DisableTCPKeepAlive {
dialer.KeepAlive = -1
dialer.KeepAliveConfig.Enable = false
} else if options.TCPKeepAliveSystemDefaults {
dialer.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: -1,
Interval: -1,
Count: -1,
}
} else {
keepIdle := time.Duration(options.TCPKeepAlive)
if keepIdle == 0 {
@@ -195,11 +185,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice(), Port: int(options.UDPBindPort)}
udpAddr4 = M.SocksaddrFrom(bindAddr, options.UDPBindPort).String()
} else if options.UDPBindPort != 0 {
udpDialer4.LocalAddr = &net.UDPAddr{IP: net.IPv4zero, Port: int(options.UDPBindPort)}
udpAddr4 = M.SocksaddrFrom(netip.IPv4Unspecified(), options.UDPBindPort).String()
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
}
var (
dialer6 = dialer
@@ -209,11 +196,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice(), Port: int(options.UDPBindPort)}
udpAddr6 = M.SocksaddrFrom(bindAddr, options.UDPBindPort).String()
} else if options.UDPBindPort != 0 {
udpDialer6.LocalAddr = &net.UDPAddr{IP: net.IPv6unspecified, Port: int(options.UDPBindPort)}
udpAddr6 = M.SocksaddrFrom(netip.IPv6Unspecified(), options.UDPBindPort).String()
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
if options.TCPMultiPath {
dialer4.SetMultipathTCP(true)
@@ -229,7 +213,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
netns: options.NetNs,
autoDetectBindFunc: autoDetectBindFunc,
connectionManager: connectionManager,
networkManager: networkManager,
networkStrategy: networkStrategy,
@@ -263,7 +246,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
return nil, E.New("domain not resolved")
}
if d.networkStrategy == nil {
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](ctx, d.netns, func() (net.Conn, error) {
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
@@ -333,19 +316,13 @@ 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 == nil {
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](ctx, d.netns, func() (net.PacketConn, error) {
listenConfig := d.udpListener
if d.autoDetectBindFunc != nil && destination.Addr.IsValid() {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return d.autoDetectBindFunc(network, destination.String(), conn)
})
}
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
if destination.IsIPv6() {
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
return listenConfig.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
} else {
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
}
}))
} else {
@@ -394,13 +371,8 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
return d.trackPacketConn(packetConn, nil)
}
func (d *DefaultDialer) UDPListenerControl() (control.Func, bool) {
egressEnabled := d.autoDetectBindFunc != nil && d.netns == ""
listenerControl := d.udpListener.Control
if egressEnabled && d.networkManager.AutoRedirectOutputMark() != 0 {
listenerControl = control.Append(listenerControl, control.UnbindFromInterface())
}
return listenerControl, egressEnabled
func (d *DefaultDialer) WireGuardControl() control.Func {
return d.udpListener.Control
}
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
+13 -53
View File
@@ -19,27 +19,6 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
return nil, false, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if len(primaryInterfaces)+len(fallbackInterfaces) == 1 {
var (
iif adapter.NetworkInterface
primary bool
)
if len(primaryInterfaces) == 1 {
iif = primaryInterfaces[0]
primary = true
} else {
iif = fallbackInterfaces[0]
}
perNetDialer := dialer
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 {
return nil, false, E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")")
}
return conn, primary, nil
}
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -114,27 +93,6 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
return nil, false, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if len(primaryInterfaces)+len(fallbackInterfaces) == 1 {
var (
iif adapter.NetworkInterface
primary bool
)
if len(primaryInterfaces) == 1 {
iif = primaryInterfaces[0]
primary = true
} else {
iif = fallbackInterfaces[0]
}
perNetDialer := dialer
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 {
return nil, false, E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")")
}
return conn, primary, nil
}
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -178,16 +136,18 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
go startRacer(fallbackCtx, false, iif)
}
var errors []error
for res := range results {
if res.error == nil {
return res.Conn, res.primary, nil
}
errors = append(errors, res.error)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, res.primary, nil
}
errors = append(errors, res.error)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
}
}
}
return nil, false, E.Errors(errors...)
}
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
@@ -224,10 +184,10 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces()
myInterfaces := networkManager.InterfaceMonitor().MyInterfaces()
if len(myInterfaces) > 0 {
myInterface := networkManager.InterfaceMonitor().MyInterface()
if myInterface != "" {
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Contains(myInterfaces, it.Name)
return it.Name != myInterface
})
}
switch strategy {
@@ -4,6 +4,6 @@ import (
"github.com/sagernet/sing/common/control"
)
type UDPListener interface {
UDPListenerControl() (control.Func, bool)
type WireGuardListener interface {
WireGuardControl() control.Func
}
+6 -1
View File
@@ -19,6 +19,11 @@ func oldWriteString(writer varbin.Writer, value string) error {
return varbin.Write(writer, binary.BigEndian, value)
}
func oldWriteItem(writer varbin.Writer, item Item) error {
//nolint:staticcheck
return varbin.Write(writer, binary.BigEndian, item)
}
func oldReadString(reader varbin.Reader) (string, error) {
//nolint:staticcheck
return varbin.ReadValue[string](reader, binary.BigEndian)
@@ -219,7 +224,7 @@ func TestGeositeWriteReadCompat(t *testing.T) {
func generateLargeItems(count int) map[string][]Item {
items := make([]Item, count)
for i := range count {
for i := 0; i < count; i++ {
items[i] = Item{
Type: ItemType(i % 4),
Value: strings.Repeat("x", i%200) + ".com",
+6 -3
View File
@@ -48,6 +48,12 @@ func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {
return reader, codes, nil
}
type geositeMetadata struct {
Code string
Index uint64
Length uint64
}
func (r *Reader) readMetadata() error {
counter := &readCounter{Reader: r.reader}
reader := bufio.NewReader(counter)
@@ -95,9 +101,6 @@ func (r *Reader) readMetadata() error {
}
func (r *Reader) Read(code string) ([]Item, error) {
r.access.Lock()
defer r.access.Unlock()
index, exists := r.domainIndex[code]
if !exists {
return nil, E.New("code ", code, " not exists!")
@@ -96,15 +96,6 @@ type appleTransport struct {
closed bool
}
func validateAppleTransport(ctx context.Context, options option.HTTPClientOptions) error {
sessionConfig, err := newAppleSessionConfig(ctx, options)
if err != nil {
return err
}
sessionConfig.close()
return nil
}
func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) {
sessionConfig, err := newAppleSessionConfig(ctx, options)
if err != nil {
@@ -120,10 +111,6 @@ func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDial
if err != nil {
return nil, err
}
err = bridge.Start()
if err != nil {
return nil, err
}
shared := &appleTransportShared{
logger: logger,
bridge: bridge,
@@ -94,9 +94,7 @@ func TestNewAppleSessionConfig(t *testing.T) {
options: option.HTTPClientOptions{
Version: 2,
DialerOptions: option.DialerOptions{
AbstractDialerOptions: option.AbstractDialerOptions{
ConnectTimeout: badoption.Duration(2 * time.Second),
},
ConnectTimeout: badoption.Duration(2 * time.Second),
},
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
TLS: &option.OutboundTLSOptions{
@@ -534,7 +532,7 @@ func TestAppleTransportRoundTripHTTPS(t *testing.T) {
}
var normalizedValues []string
for _, value := range observed.values {
for part := range strings.SplitSeq(value, ",") {
for _, part := range strings.Split(value, ",") {
normalizedValues = append(normalizedValues, strings.TrimSpace(part))
}
}
@@ -689,7 +687,7 @@ func TestAppleTransportCancellationRecovery(t *testing.T) {
},
})
for index := range appleHTTPRecoveryLoops {
for index := 0; index < appleHTTPRecoveryLoops; index++ {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
request := newAppleHTTPRequestWithContext(t, ctx, http.MethodGet, server.URL("/block"), nil)
response, err := transport.RoundTrip(request)
@@ -11,10 +11,6 @@ import (
N "github.com/sagernet/sing/common/network"
)
func validateAppleTransport(ctx context.Context, options option.HTTPClientOptions) error {
return E.New("Apple HTTP engine is not available on non-Apple platforms")
}
func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) {
return nil, E.New("Apple HTTP engine is not available on non-Apple platforms")
}
+7 -5
View File
@@ -35,11 +35,11 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string,
var cheapRebuild bool
switch options.Engine {
case C.TLSEngineApple:
err = validateAppleTransport(ctx, options)
if err != nil {
return nil, err
inner, transportErr := newAppleTransport(ctx, logger, rawDialer, options)
if transportErr != nil {
return nil, transportErr
}
return &ManagedTransport{
managedTransport := &ManagedTransport{
dialer: rawDialer,
headers: headers,
host: host,
@@ -47,7 +47,9 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string,
factory: func() (innerTransport, error) {
return newAppleTransport(ctx, logger, rawDialer, options)
},
}, nil
}
managedTransport.epoch.Store(&transportEpoch{transport: inner})
return managedTransport, nil
case "", C.TLSEngineGo:
cheapRebuild = true
default:
+3 -3
View File
@@ -131,7 +131,7 @@ func (j *ClientHello) parseHandshake(hs []byte) error {
return &ParseError{LengthErr, 7}
}
for i := range numCiphers {
for i := 0; i < numCiphers; i++ {
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
cipherSuites = append(cipherSuites, cipherSuite)
}
@@ -234,7 +234,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 16}
}
for i := range numCurves {
for i := 0; i < numCurves; i++ {
ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])
ellipticCurves = append(ellipticCurves, ecType)
}
@@ -256,7 +256,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 18}
}
for i := range numPF {
for i := 0; i < numPF; i++ {
ellipticCurvePF[i] = uint8(sex[i])
}
case versionExtensionType:
-13
View File
@@ -1,13 +0,0 @@
#include <jni.h>
#include <stdint.h>
static JavaVM *javaVM;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
javaVM = vm;
return JNI_VERSION_1_6;
}
uintptr_t box_jni_vm(void) {
return (uintptr_t) javaVM;
}
-13
View File
@@ -1,13 +0,0 @@
//go:build android
package jni
/*
#include <stdint.h>
extern uintptr_t box_jni_vm(void);
*/
import "C"
func VM() uintptr {
return uintptr(C.box_jni_vm())
}
-7
View File
@@ -1,7 +0,0 @@
//go:build !android || !cgo
package jni
func VM() uintptr {
return 0
}
+53 -1
View File
@@ -6,7 +6,48 @@
package ktls
import "golang.org/x/crypto/cryptobyte"
import (
"fmt"
"golang.org/x/crypto/cryptobyte"
)
// The marshalingFunction type is an adapter to allow the use of ordinary
// functions as cryptobyte.MarshalingValue.
type marshalingFunction func(b *cryptobyte.Builder) error
func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {
return f(b)
}
// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If
// the length of the sequence is not the value specified, it produces an error.
func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {
b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {
if len(v) != n {
return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v))
}
b.AddBytes(v)
return nil
}))
}
// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.
func addUint64(b *cryptobyte.Builder, v uint64) {
b.AddUint32(uint32(v >> 32))
b.AddUint32(uint32(v))
}
// readUint64 decodes a big-endian, 64-bit value into out and advances over it.
// It reports whether the read was successful.
func readUint64(s *cryptobyte.String, out *uint64) bool {
var hi, lo uint32
if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {
return false
}
*out = uint64(hi)<<32 | uint64(lo)
return true
}
// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String.
@@ -20,6 +61,12 @@ func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))
}
// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String.
func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))
}
type keyUpdateMsg struct {
updateRequested bool
}
@@ -78,6 +125,11 @@ const (
typeMessageHash uint8 = 254 // synthetic message
)
// TLS compression types.
const (
compressionNone uint8 = 0
)
// TLS extension numbers
const (
extensionServerName uint16 = 0
+73
View File
@@ -77,5 +77,78 @@ func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {
if !c.kernelTx {
return c.rawConn.WriteRecordLocked(typ, data)
}
/*for len(data) > 0 {
m := len(data)
if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {
m = maxPayload
}
_, err = c.writeKernelRecord(typ, data[:m])
if err != nil {
return
}
n += m
data = data[m:]
}*/
return c.writeKernelRecord(typ, data)
}
const (
// tcpMSSEstimate is a conservative estimate of the TCP maximum segment
// size (MSS). A constant is used, rather than querying the kernel for
// the actual MSS, to avoid complexity. The value here is the IPv6
// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40
// bytes) and a TCP header with timestamps (32 bytes).
tcpMSSEstimate = 1208
// recordSizeBoostThreshold is the number of bytes of application data
// sent after which the TLS record size will be increased to the
// maximum.
recordSizeBoostThreshold = 128 * 1024
)
func (c *Conn) maxPayloadSizeForWrite(typ uint16) int {
if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {
return maxPlaintext
}
if *c.rawConn.PacketsSent >= recordSizeBoostThreshold {
return maxPlaintext
}
// Subtract TLS overheads to get the maximum payload size.
payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()
if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {
switch ciph := rawCipher.(type) {
case cipher.Stream:
payloadBytes -= (*c.rawConn.Out.Mac).Size()
case cipher.AEAD:
payloadBytes -= ciph.Overhead()
/*case cbcMode:
blockSize := ciph.BlockSize()
// The payload must fit in a multiple of blockSize, with
// room for at least one padding byte.
payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1
// The RawMac is appended before padding so affects the
// payload size directly.
payloadBytes -= c.out.mac.Size()*/
default:
panic("unknown cipher type")
}
}
if *c.rawConn.Vers == tls.VersionTLS13 {
payloadBytes-- // encrypted ContentType
}
// Allow packet growth in arithmetic progression up to max.
pkt := *c.rawConn.PacketsSent
*c.rawConn.PacketsSent++
if pkt > 1000 {
return maxPlaintext // avoid overflow in multiply below
}
n := payloadBytes * int(pkt+1)
if n > maxPlaintext {
n = maxPlaintext
}
return n
}
+24 -40
View File
@@ -16,7 +16,6 @@ import (
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/vishvananda/netns"
)
@@ -107,7 +106,7 @@ func (l *Listener) Start() error {
} else {
listenAddrString = listenAddr.String()
}
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS, nil)
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
if err != nil {
return E.Cause(err, "initialize system proxy")
}
@@ -144,45 +143,30 @@ func (l *Listener) ListenOptions() option.ListenOptions {
return l.listenOptions
}
func ListenNetworkNamespace[T any](ctx context.Context, nameOrPath string, block func() (T, error)) (T, error) {
if nameOrPath == "" {
return block()
}
manager := service.FromContext[adapter.NetworkNamespaceManager](ctx)
if manager != nil {
nameOrPath = manager.ResolvePath(nameOrPath)
}
type blockResult struct {
value T
err error
}
resultChannel := make(chan blockResult, 1)
go func() {
func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) {
if nameOrPath != "" {
runtime.LockOSThread()
value, err := listenNetworkNamespaceThread(nameOrPath, block)
resultChannel <- blockResult{value, err}
}()
result := <-resultChannel
return result.value, result.err
}
func listenNetworkNamespaceThread[T any](nameOrPath string, block func() (T, error)) (T, error) {
var (
targetNs netns.NsHandle
err error
)
if strings.HasPrefix(nameOrPath, "/") {
targetNs, err = netns.GetFromPath(nameOrPath)
} else {
targetNs, err = netns.GetFromName(nameOrPath)
}
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath)
defer runtime.UnlockOSThread()
currentNs, err := netns.Get()
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get current netns")
}
defer currentNs.Close()
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(nameOrPath, "/") {
targetNs, err = netns.GetFromPath(nameOrPath)
} else {
targetNs, err = netns.GetFromName(nameOrPath)
}
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath)
}
}
return block()
}
+1 -1
View File
@@ -65,7 +65,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
})
})
}
tcpListener, err := ListenNetworkNamespace[net.Listener](l.ctx, l.listenOptions.NetNs, func() (net.Listener, error) {
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
if l.listenOptions.TCPFastOpen {
var tfoConfig tfo.ListenConfig
tfoConfig.ListenConfig = listenConfig
+4 -7
View File
@@ -22,11 +22,8 @@ import (
const udpOutputBatchSize = 128
func (l *Listener) ListenUDP() (net.PacketConn, error) {
return l.ListenUDPWithConfig(net.ListenConfig{})
}
func (l *Listener) ListenUDPWithConfig(listenConfig net.ListenConfig) (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var listenConfig net.ListenConfig
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
@@ -52,7 +49,7 @@ func (l *Listener) ListenUDPWithConfig(listenConfig net.ListenConfig) (net.Packe
})
})
}
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.ctx, l.listenOptions.NetNs, func() (net.PacketConn, error) {
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
})
if err != nil {
@@ -65,7 +62,7 @@ func (l *Listener) ListenUDPWithConfig(listenConfig net.ListenConfig) (net.Packe
}
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
return ListenNetworkNamespace[net.Conn](l.ctx, l.listenOptions.NetNs, func() (net.Conn, error) {
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
if l.listenOptions.BindInterface != "" {
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
@@ -80,7 +77,7 @@ func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network s
}
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
return ListenNetworkNamespace[net.PacketConn](l.ctx, l.listenOptions.NetNs, func() (net.PacketConn, error) {
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
-194
View File
@@ -1,194 +0,0 @@
package netns
import (
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/sagernet/netlink"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
vnetns "github.com/vishvananda/netns"
)
type Manager struct {
logger logger.ContextLogger
namespaces []option.NetworkNamespace
holderArgs []string
paths map[string]string
holders []*holder
}
type holder struct {
command *exec.Cmd
pipeWriter *os.File
pidFile string
}
func NewManager(logger logger.ContextLogger, namespaces []option.NetworkNamespace, holderArgs []string) (*Manager, error) {
paths := make(map[string]string)
for _, namespace := range namespaces {
if namespace.Tag == "" {
return nil, E.New("network namespace: missing tag")
}
_, duplicated := paths[namespace.Tag]
if duplicated {
return nil, E.New("network namespace: duplicated tag: ", namespace.Tag)
}
switch namespace.Type {
case C.NetNsTypeDefault:
if namespace.DefaultOptions.Path == "" {
return nil, E.New("network namespace[", namespace.Tag, "]: missing path")
}
paths[namespace.Tag] = namespace.DefaultOptions.Path
case C.NetNsTypeUnshare:
paths[namespace.Tag] = ""
}
}
return &Manager{
logger: logger,
namespaces: namespaces,
holderArgs: holderArgs,
paths: paths,
}, nil
}
func (m *Manager) Name() string {
return "netns"
}
func (m *Manager) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateInitialize {
return nil
}
for _, namespace := range m.namespaces {
switch namespace.Type {
case C.NetNsTypeDefault:
path := namespace.DefaultOptions.Path
if !strings.HasPrefix(path, "/") {
path = "/run/netns/" + path
}
_, err := os.Stat(path)
if err != nil {
return E.Cause(err, "network namespace[", namespace.Tag, "]")
}
case C.NetNsTypeUnshare:
err := m.startNamespace(namespace)
if err != nil {
return E.Cause(err, "network namespace[", namespace.Tag, "]")
}
}
}
return nil
}
func (m *Manager) startNamespace(namespace option.NetworkNamespace) error {
if len(m.holderArgs) == 0 {
return E.New("unshare network namespace is only supported in `sing-box run`")
}
created, err := m.startHolder(namespace.UnshareOptions.PidFile)
if err != nil {
return err
}
m.holders = append(m.holders, created)
pid := created.command.Process.Pid
if created.pidFile != "" {
err = os.WriteFile(created.pidFile, []byte(strconv.Itoa(pid)+"\n"), 0o644)
if err != nil {
return E.Cause(err, "write pid file")
}
}
m.paths[namespace.Tag] = netnsPath(pid)
m.logger.Info("created network namespace[", namespace.Tag, "], holder pid: ", pid)
if os.Geteuid() == 0 {
m.logger.Info("enter network namespace[", namespace.Tag, "] with: nsenter -n -t ", pid)
} else {
m.logger.Info("enter network namespace[", namespace.Tag, "] with: nsenter -U --preserve-credentials -n -t ", pid)
}
return nil
}
func (m *Manager) Close() error {
for _, created := range m.holders {
created.pipeWriter.Close()
if created.pidFile != "" {
os.Remove(created.pidFile)
}
}
m.holders = nil
return nil
}
func (m *Manager) ResolvePath(nameOrPath string) string {
path, loaded := m.paths[nameOrPath]
if loaded && path != "" {
return path
}
return nameOrPath
}
func netnsPath(pid int) string {
return "/proc/" + strconv.Itoa(pid) + "/ns/net"
}
func (m *Manager) startHolder(pidFile string) (*holder, error) {
pipeReader, pipeWriter, err := os.Pipe()
if err != nil {
return nil, err
}
command := exec.Command(m.holderArgs[0], m.holderArgs[1:]...)
command.Stdin = pipeReader
command.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNET,
}
err = command.Start()
pipeReader.Close()
if err != nil {
pipeWriter.Close()
return nil, E.Cause(err, "start holder process")
}
go command.Wait()
err = setupNamespace(command.Process.Pid)
if err != nil {
pipeWriter.Close()
return nil, err
}
return &holder{command: command, pipeWriter: pipeWriter, pidFile: pidFile}, nil
}
func setupNamespace(pid int) error {
resultChannel := make(chan error, 1)
go func() {
runtime.LockOSThread()
resultChannel <- setupNamespaceThread(pid)
}()
return <-resultChannel
}
func setupNamespaceThread(pid int) error {
targetNs, err := vnetns.GetFromPath(netnsPath(pid))
if err != nil {
return E.Cause(err, "open created netns")
}
defer targetNs.Close()
err = vnetns.Set(targetNs)
if err != nil {
return E.Cause(err, "enter created netns")
}
loopbackLink, err := netlink.LinkByName("lo")
if err != nil {
return E.Cause(err, "find lo")
}
err = netlink.LinkSetUp(loopbackLink)
if err != nil {
return E.Cause(err, "set lo up")
}
return nil
}
-35
View File
@@ -1,35 +0,0 @@
//go:build !linux
package netns
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
type Manager struct{}
func NewManager(logger logger.ContextLogger, namespaces []option.NetworkNamespace, holderArgs []string) (*Manager, error) {
if len(namespaces) > 0 {
return nil, E.New("network namespaces are only supported on Linux")
}
return &Manager{}, nil
}
func (m *Manager) Name() string {
return "netns"
}
func (m *Manager) Start(stage adapter.StartStage) error {
return nil
}
func (m *Manager) Close() error {
return nil
}
func (m *Manager) ResolvePath(nameOrPath string) string {
return nameOrPath
}
-85
View File
@@ -1,85 +0,0 @@
//go:build linux
package netns
import (
"bufio"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
)
func TestUnshareNamespace(t *testing.T) {
if os.Getenv("NETNS_TEST_HOLDER") == "1" {
Hold()
}
pipeReader, pipeWriter, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
defer pipeReader.Close()
defer pipeWriter.Close()
os.Setenv("NETNS_TEST_HOLDER", "1")
defer os.Unsetenv("NETNS_TEST_HOLDER")
manager, err := NewManager(logger.NOP(), []option.NetworkNamespace{{
Type: C.NetNsTypeUnshare,
Tag: "test",
UnshareOptions: option.UnshareNetworkNamespaceOptions{
PidFile: "/proc/self/fd/" + F.ToString(pipeWriter.Fd()),
},
}}, []string{"/proc/self/exe", "-test.run=^TestUnshareNamespace$"})
if err != nil {
t.Fatal(err)
}
err = manager.Start(adapter.StartStateInitialize)
if err != nil {
t.Fatal(err)
}
defer manager.Close()
pipeReader.SetReadDeadline(time.Now().Add(10 * time.Second))
pidLine, err := bufio.NewReader(pipeReader).ReadString('\n')
if err != nil {
t.Fatal("read pid from pipe: ", err)
}
pid, err := strconv.Atoi(strings.TrimSuffix(pidLine, "\n"))
if err != nil {
t.Fatal("parse pid: ", err)
}
resolvedPath := manager.ResolvePath("test")
if resolvedPath != netnsPath(pid) {
t.Fatal("resolved path ", resolvedPath, " does not match pid ", pid)
}
currentNs, err := os.Readlink("/proc/thread-self/ns/net")
if err != nil {
t.Fatal(err)
}
holderNs, err := os.Readlink(resolvedPath)
if err != nil {
t.Fatal("holder netns not accessible: ", err)
}
if currentNs == holderNs {
t.Fatal("holder is in the current netns")
}
err = manager.Close()
if err != nil {
t.Fatal(err)
}
for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline); time.Sleep(10 * time.Millisecond) {
_, err = os.Stat("/proc/" + strconv.Itoa(pid))
if err != nil {
return
}
}
t.Fatal("holder process did not exit after close")
}
-19
View File
@@ -1,19 +0,0 @@
package netns
import (
"os"
"github.com/sagernet/sing-box/adapter"
)
var _ adapter.NetworkNamespaceManager = (*Manager)(nil)
func Hold() {
buffer := make([]byte, 1)
for {
_, err := os.Stdin.Read(buffer)
if err != nil {
os.Exit(0)
}
}
}
+9 -4
View File
@@ -10,7 +10,6 @@ import (
"net/http"
"net/http/httptrace"
"net/url"
"slices"
"sort"
"strings"
"sync"
@@ -514,7 +513,10 @@ func (r *directionRunner) swapIntervalProbeValues() []float64 {
}
func (r *directionRunner) setResponsivenessWindow(currentInterval int) {
lower := max(currentInterval-settings.movingAvgDistance+1, 0)
lower := currentInterval - settings.movingAvgDistance + 1
if lower < 0 {
lower = 0
}
r.probeMu.Lock()
r.responsivenessWindow = &intervalWindow{lower: lower, upper: currentInterval}
r.probeMu.Unlock()
@@ -527,7 +529,10 @@ func (r *directionRunner) recordThroughput(interval int, bps float64) {
}
func (r *directionRunner) setThroughputWindow(currentInterval int) {
lower := max(currentInterval-settings.movingAvgDistance+1, 0)
lower := currentInterval - settings.movingAvgDistance + 1
if lower < 0 {
lower = 0
}
r.probeMu.Lock()
r.throughputWindow = &intervalWindow{lower: lower, upper: currentInterval}
r.probeMu.Unlock()
@@ -951,7 +956,7 @@ func measureIdleLatency(ctx context.Context, factory MeasurementClientFactory, c
maxProbeBytes = measurement.bytes
}
}
slices.Sort(latencies)
sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] })
return int32(latencies[len(latencies)/2]), maxProbeBytes, nil
}
-1
View File
@@ -14,7 +14,6 @@ import (
type Searcher interface {
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
ResetCache()
Close() error
}
-7
View File
@@ -7,7 +7,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ Searcher = (*androidSearcher)(nil)
@@ -17,15 +16,9 @@ type androidSearcher struct {
}
func NewSearcher(config Config) (Searcher, error) {
if config.PackageManager == nil {
return nil, E.New("missing package manager")
}
return &androidSearcher{config.PackageManager}, nil
}
func (s *androidSearcher) ResetCache() {
}
func (s *androidSearcher) Close() error {
return nil
}
-4
View File
@@ -20,10 +20,6 @@ func NewSearcher(_ Config) (Searcher, error) {
return &darwinSearcher{}, nil
}
func (d *darwinSearcher) ResetCache() {
sharedDarwinConnectionFinder.resetCache()
}
func (d *darwinSearcher) Close() error {
return nil
}
+1 -7
View File
@@ -81,7 +81,7 @@ func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, des
source = normalizeDarwinAddrPort(source)
destination = normalizeDarwinAddrPort(destination)
var lastOwner *adapter.ConnectionOwner
for attempt := range 2 {
for attempt := 0; attempt < 2; attempt++ {
snapshot, fromCache, err := f.loadSnapshot(networkName, attempt > 0)
if err != nil {
return nil, err
@@ -119,12 +119,6 @@ func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, des
return nil, ErrNotFound
}
func (f *darwinConnectionFinder) resetCache() {
f.access.Lock()
defer f.access.Unlock()
clear(f.snapshots)
}
func (f *darwinConnectionFinder) loadSnapshot(network string, forceRefresh bool) (darwinSnapshot, bool, error) {
f.access.Lock()
defer f.access.Unlock()
-4
View File
@@ -35,10 +35,6 @@ func NewSearcher(config Config) (Searcher, error) {
return searcher, nil
}
func (s *linuxSearcher) ResetCache() {
s.processPathCache.cache.Purge()
}
func (s *linuxSearcher) Close() error {
var errs []error
for _, conn := range s.diagConns {
+3 -4
View File
@@ -1,6 +1,5 @@
//go:build linux
//nolint:unused
package process
import (
@@ -38,7 +37,7 @@ type socketDiagConn struct {
}
type uidProcessPathCache struct {
cache *freelru.Cache[uint32, *uidProcessPaths]
cache freelru.Cache[uint32, *uidProcessPaths]
}
type uidProcessPaths struct {
@@ -85,7 +84,7 @@ func socketDiagSettings(network string, source netip.AddrPort) (family, protocol
}
func newUIDProcessPathCache(ttl time.Duration) *uidProcessPathCache {
cache := common.Must1(freelru.New[uint32, *uidProcessPaths](64, maphash.NewHasher[uint32]().Hash32, true))
cache := common.Must1(freelru.NewSharded[uint32, *uidProcessPaths](64, maphash.NewHasher[uint32]().Hash32))
cache.SetLifetime(ttl)
return &uidProcessPathCache{cache: cache}
}
@@ -118,7 +117,7 @@ func (c *socketDiagConn) query(source netip.AddrPort, destination netip.AddrPort
c.access.Lock()
defer c.access.Unlock()
request := packSocketDiagRequest(c.family, c.protocol, source, destination, false)
for range 2 {
for attempt := 0; attempt < 2; attempt++ {
err = c.ensureOpenLocked()
if err != nil {
return 0, 0, E.Cause(err, "dial netlink")
-3
View File
@@ -28,9 +28,6 @@ func initWin32API() error {
return winiphlpapi.LoadExtendedTable()
}
func (s *windowsSearcher) ResetCache() {
}
func (s *windowsSearcher) Close() error {
return nil
}
+9 -12
View File
@@ -34,26 +34,23 @@ type Bridge struct {
func New(ctx context.Context, logger logger.ContextLogger, tag string, dialer N.Dialer) (*Bridge, error) {
username := randomHex(16)
password := randomHex(16)
return &Bridge{
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
return nil, err
}
bridge := &Bridge{
ctx: ctx,
logger: logger,
tag: tag,
dialer: dialer,
connection: service.FromContext[adapter.ConnectionManager](ctx),
tcpListener: tcpListener,
username: username,
password: password,
authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}),
}, nil
}
func (b *Bridge) Start() error {
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
return err
}
b.tcpListener = tcpListener
go b.acceptLoop()
return nil
go bridge.acceptLoop()
return bridge, nil
}
func randomHex(size int) string {
@@ -75,7 +72,7 @@ func (b *Bridge) Password() string {
}
func (b *Bridge) Close() error {
return common.Close(common.PtrOrNil(b.tcpListener))
return common.Close(b.tcpListener)
}
func (b *Bridge) acceptLoop() {
+5 -5
View File
@@ -109,12 +109,12 @@ type schCredentials struct {
}
type tlsParameters struct {
_ uint32 // cAlpnIds
_ uintptr // rgstrAlpnIds
cAlpnIds uint32
rgstrAlpnIds uintptr
grbitDisabledProtocols uint32
_ uint32 // cDisabledCrypto
_ uintptr // pDisabledCrypto
_ uint32 // dwFlags
cDisabledCrypto uint32
pDisabledCrypto uintptr
dwFlags uint32
}
type secPkgContextStreamSizes struct {
+1 -1
View File
@@ -20,7 +20,7 @@ type AndroidSystemProxy struct {
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*AndroidSystemProxy, error) {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*AndroidSystemProxy, error) {
userId := os.Getuid()
var (
useRish bool
+2 -2
View File
@@ -24,7 +24,7 @@ type DarwinSystemProxy struct {
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*DarwinSystemProxy, error) {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
if interfaceMonitor == nil {
return nil, E.New("missing interface monitor")
@@ -109,7 +109,7 @@ func getInterfaceDisplayName(name string) (string, error) {
if err != nil {
return "", err
}
for deviceSpan := range strings.SplitSeq(string(content), "Ethernet Address") {
for _, deviceSpan := range strings.Split(string(content), "Ethernet Address") {
if strings.Contains(deviceSpan, "Device: "+name) {
substr := "Hardware Port: "
deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):]
+29 -31
View File
@@ -16,31 +16,15 @@ import (
)
type LinuxSystemProxy struct {
execute func(name string, args ...string) error
hasGSettings bool
kWriteConfigCmd string
sudoUser string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*LinuxSystemProxy, error) {
var sudoUser string
if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER")
}
return NewLinuxSystemProxy(serverAddr, supportSOCKS, func(name string, args ...string) error {
if os.Getuid() != 0 {
return shell.Exec(name, args...).Attach().Run()
} else if sudoUser != "" {
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else {
return E.New("set system proxy: unable to set as root")
}
})
}
func NewLinuxSystemProxy(serverAddr M.Socksaddr, supportSOCKS bool, execute func(name string, args ...string) error) (*LinuxSystemProxy, error) {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
kWriteConfigCmds := []string{
"kwriteconfig5",
@@ -53,13 +37,17 @@ func NewLinuxSystemProxy(serverAddr M.Socksaddr, supportSOCKS bool, execute func
break
}
}
var sudoUser string
if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER")
}
if !hasGSettings && kWriteConfigCmd == "" {
return nil, E.New("unsupported desktop environment")
}
return &LinuxSystemProxy{
execute: execute,
hasGSettings: hasGSettings,
kWriteConfigCmd: kWriteConfigCmd,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
@@ -71,7 +59,7 @@ func (p *LinuxSystemProxy) IsEnabled() bool {
func (p *LinuxSystemProxy) Enable() error {
if p.hasGSettings {
err := p.execute("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
if err != nil {
return err
}
@@ -83,17 +71,17 @@ func (p *LinuxSystemProxy) Enable() error {
if err != nil {
return err
}
err = p.execute("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(p.supportSOCKS))
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(p.supportSOCKS))
if err != nil {
return err
}
err = p.execute("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
if err != nil {
return err
}
}
if p.kWriteConfigCmd != "" {
err := p.execute(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
if err != nil {
return err
}
@@ -105,11 +93,11 @@ func (p *LinuxSystemProxy) Enable() error {
if err != nil {
return err
}
err = p.execute(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
err = p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
if err != nil {
return err
}
err = p.execute("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return err
}
@@ -120,17 +108,17 @@ func (p *LinuxSystemProxy) Enable() error {
func (p *LinuxSystemProxy) Disable() error {
if p.hasGSettings {
err := p.execute("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
if err != nil {
return err
}
}
if p.kWriteConfigCmd != "" {
err := p.execute(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
if err != nil {
return err
}
err = p.execute("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return err
}
@@ -139,13 +127,23 @@ func (p *LinuxSystemProxy) Disable() error {
return nil
}
func (p *LinuxSystemProxy) runAsUser(name string, args ...string) error {
if os.Getuid() != 0 {
return shell.Exec(name, args...).Attach().Run()
} else if p.sudoUser != "" {
return shell.Exec("su", "-", p.sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else {
return E.New("set system proxy: unable to set as root")
}
}
func (p *LinuxSystemProxy) setGnomeProxy(proxyTypes ...string) error {
for _, proxyType := range proxyTypes {
err := p.execute("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", p.serverAddr.AddrString())
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", p.serverAddr.AddrString())
if err != nil {
return err
}
err = p.execute("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(p.serverAddr.Port))
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(p.serverAddr.Port))
if err != nil {
return err
}
@@ -161,7 +159,7 @@ func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
} else {
proxyUrl = "http://" + p.serverAddr.String()
}
err := p.execute(
err := p.runAsUser(
p.kWriteConfigCmd,
"--file",
"kioslaverc",
+1 -1
View File
@@ -9,6 +9,6 @@ import (
M "github.com/sagernet/sing/common/metadata"
)
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (SystemProxy, error) {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) {
return nil, os.ErrInvalid
}
+2 -5
View File
@@ -2,7 +2,6 @@ package settings
import (
"context"
"strings"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/wininet"
@@ -11,15 +10,13 @@ import (
type WindowsSystemProxy struct {
serverAddr M.Socksaddr
supportSOCKS bool
bypassDomain []string
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*WindowsSystemProxy, error) {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) {
return &WindowsSystemProxy{
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
bypassDomain: bypassDomain,
}, nil
}
@@ -28,7 +25,7 @@ func (p *WindowsSystemProxy) IsEnabled() bool {
}
func (p *WindowsSystemProxy) Enable() error {
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), strings.Join(p.bypassDomain, ";"))
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), "")
if err != nil {
return err
}
+2 -2
View File
@@ -40,14 +40,14 @@ func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
defer cancel()
cmObj := m.conn.Object("net.connman", "/")
var services []any
var services []interface{}
err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
if err != nil {
return adapter.WIFIState{}
}
for _, service := range services {
servicePair, ok := service.([]any)
servicePair, ok := service.([]interface{})
if !ok || len(servicePair) != 2 {
continue
}
+6 -7
View File
@@ -1,4 +1,3 @@
//nolint:unused
package settings
import (
@@ -74,13 +73,13 @@ func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
scanner := bufio.NewScanner(strings.NewReader(status))
for scanner.Scan() {
line := scanner.Text()
if after, ok := strings.CutPrefix(line, "wpa_state="); ok {
state := after
if strings.HasPrefix(line, "wpa_state=") {
state := strings.TrimPrefix(line, "wpa_state=")
connected = state == "COMPLETED"
} else if after, ok := strings.CutPrefix(line, "ssid="); ok {
ssid = after
} else if after, ok := strings.CutPrefix(line, "bssid="); ok {
bssid = after
} else if strings.HasPrefix(line, "ssid=") {
ssid = strings.TrimPrefix(line, "ssid=")
} else if strings.HasPrefix(line, "bssid=") {
bssid = strings.TrimPrefix(line, "bssid=")
}
}
-1
View File
@@ -1,6 +1,5 @@
//go:build !linux && !windows
//nolint:unused
package settings
import (
+3 -2
View File
@@ -54,8 +54,9 @@ type xorNonceAEAD struct {
aead cipher.AEAD
}
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce {

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