Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ee56ae7f5 | |||
| 48c65a2f8a | |||
| 772ab58f8c | |||
| bb60b58f00 | |||
| 85889fc701 | |||
| 98b21227fa | |||
| fdec2fe051 | |||
| e171852b19 | |||
| 2a533b0daa | |||
| ddc20355cf | |||
| abedea4e57 | |||
| 1bad8d87eb | |||
| 60a5f874e2 | |||
| 610aa9432d | |||
| 78f502d016 | |||
| b418ee1a98 | |||
| fd1624e426 | |||
| d9c9edd6f8 | |||
| 1610c18a2f | |||
| 3d3c8a2fe9 | |||
| 968bbd832c | |||
| d06d3bf2a4 | |||
| fbc1ca36e6 | |||
| 0f719a0715 | |||
| 817f9c6364 | |||
| 5cd421401d | |||
| acfb90a38e | |||
| 6b51bd6778 | |||
| b358fdd564 | |||
| 6ecd3deef5 | |||
| a8b8f15a76 | |||
| 3c9c4aee07 | |||
| 32f56cce07 | |||
| 4b15fccb78 | |||
| 9bffb64bbd | |||
| 4a68bc9274 | |||
| fee6afdbf5 | |||
| 90a642ef96 | |||
| 9bee53225c | |||
| 08ce083b9b | |||
| 6ef38042f3 | |||
| 5a618c6b68 | |||
| 0d7230e42c | |||
| 0319b22c76 | |||
| e6f5c2438b | |||
| 8fb019164e | |||
| 104c7ae24d | |||
| 3828b4f30e | |||
| 08260fa770 | |||
| 5779b46ca6 | |||
| 012db0ef0e | |||
| 684ba79113 | |||
| e714efa792 | |||
| e75e1c98a9 | |||
| 524578a635 | |||
| eade67726a | |||
| ec75e5ec0a | |||
| a7b02f9cb2 | |||
| ce6d6838a8 | |||
| ccc2742d2b | |||
| 0b8f380817 | |||
| 5ee373c549 | |||
| 0f6d110dad | |||
| 9e8f13c0ac | |||
| e1a7ab3df3 | |||
| ff94634b52 | |||
| 26ddb928d9 | |||
| 0155352ff1 | |||
| 83fa58f60a | |||
| 4f6d0ffafc | |||
| 1c02d7e8b0 | |||
| c57e86427c | |||
| d3575cc8c2 |
@@ -1 +1 @@
|
||||
617d38f41f935b46a68f550d9add2e38abb3f168
|
||||
cd9fcba9981bf69c1aab541887d2758d84dc31f3
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
- 其他
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="1.25.12"
|
||||
VERSION="1.25.9"
|
||||
PATCH_COMMITS=(
|
||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
||||
|
||||
@@ -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
|
||||
|
||||
+178
-904
File diff suppressed because it is too large
Load Diff
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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$
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package adapter
|
||||
|
||||
type NetworkNamespaceManager interface {
|
||||
ResolvePath(nameOrPath string) string
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build !with_usbip || !(linux || (darwin && cgo) || windows)
|
||||
|
||||
package adapter
|
||||
|
||||
type USBIPDynamicServer interface {
|
||||
usbipNotIncluded()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule clients/android updated: aa686b2f1a...3b3883ef2c
+1
-1
Submodule clients/apple updated: 794eb1741f...e5d6ab4c77
Submodule clients/desktop deleted from cebee0d527
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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, " ")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build !android || !cgo
|
||||
|
||||
package certificate
|
||||
|
||||
import "crypto/x509"
|
||||
|
||||
func systemCertificates() []*x509.Certificate {
|
||||
return nil
|
||||
}
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build !android || !cgo
|
||||
|
||||
package jni
|
||||
|
||||
func VM() uintptr {
|
||||
return 0
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -28,9 +28,6 @@ func initWin32API() error {
|
||||
return winiphlpapi.LoadExtendedTable()
|
||||
}
|
||||
|
||||
func (s *windowsSearcher) ResetCache() {
|
||||
}
|
||||
|
||||
func (s *windowsSearcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
//go:build !linux && !windows
|
||||
|
||||
//nolint:unused
|
||||
package settings
|
||||
|
||||
import (
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user