mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
75 Commits
v1.10.0-be
...
dev-next-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47736b27ba | ||
|
|
0b2c7ec35c | ||
|
|
e537c56b6b | ||
|
|
b456aff4ac | ||
|
|
4da652ff02 | ||
|
|
262727ec6c | ||
|
|
81dc9e7698 | ||
|
|
d876077281 | ||
|
|
0df81c297b | ||
|
|
b460484e43 | ||
|
|
0e511791e8 | ||
|
|
9585c53e9f | ||
|
|
d66d5cd457 | ||
|
|
8c143feec8 | ||
|
|
419058f466 | ||
|
|
1a6047a61b | ||
|
|
327bb35ddd | ||
|
|
6ed9a06394 | ||
|
|
b80ec55ba0 | ||
|
|
08718112ae | ||
|
|
956ee361df | ||
|
|
e93d0408be | ||
|
|
137832ff3e | ||
|
|
3ede29fb6d | ||
|
|
82ab68b542 | ||
|
|
e55723d84d | ||
|
|
2f4d2d97f9 | ||
|
|
926d6f769e | ||
|
|
846777cd0c | ||
|
|
06533b7a3b | ||
|
|
4a95558c53 | ||
|
|
e39a28ed5a | ||
|
|
b2c708a3e6 | ||
|
|
a9209bb3e5 | ||
|
|
9dc3bb975a | ||
|
|
3a7acaa92a | ||
|
|
6bebe2483b | ||
|
|
93cf134995 | ||
|
|
ff7d8c9ba8 | ||
|
|
50f07b42f6 | ||
|
|
db3a0c636d | ||
|
|
fec38f85cd | ||
|
|
dcb0141646 | ||
|
|
f4f5a3c925 | ||
|
|
9b8d6c1b73 | ||
|
|
2f776168de | ||
|
|
923d3222b0 | ||
|
|
bda93d516b | ||
|
|
7eec3fb57a | ||
|
|
b1d75812c5 | ||
|
|
d44e7d9834 | ||
|
|
369bc7cea3 | ||
|
|
4b7a83da16 | ||
|
|
0f7154afbd | ||
|
|
a06d10c3bc | ||
|
|
63cc6cc76c | ||
|
|
d55c5b5cab | ||
|
|
b624c2dcc7 | ||
|
|
9415444ebd | ||
|
|
95606191d8 | ||
|
|
e586d9e9bc | ||
|
|
8c7eaa4477 | ||
|
|
8464c8cb7c | ||
|
|
39d7127651 | ||
|
|
e2077009c4 | ||
|
|
700a8eb425 | ||
|
|
3b0cba0852 | ||
|
|
f5554dd8b8 | ||
|
|
4d0362d530 | ||
|
|
97ccd2ca04 | ||
|
|
1ed6654ad4 | ||
|
|
5385f75f53 | ||
|
|
ad97d4e11f | ||
|
|
09d4e91b77 | ||
|
|
3dbdda9555 |
10
.github/workflows/debug.yml
vendored
10
.github/workflows/debug.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
|
||||
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@@ -28,10 +28,21 @@ jobs:
|
||||
- linux/riscv64
|
||||
- linux/s390x
|
||||
steps:
|
||||
- name: Get commit to build
|
||||
id: ref
|
||||
run: |-
|
||||
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||
ref="${{ github.ref_name }}"
|
||||
else
|
||||
ref="${{ github.event.inputs.tag }}"
|
||||
fi
|
||||
echo "ref=$ref"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
|
||||
@@ -26,6 +26,7 @@ builds:
|
||||
- linux_arm_7
|
||||
- linux_s390x
|
||||
- linux_riscv64
|
||||
- linux_mips64le
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
@@ -48,10 +49,19 @@ nfpms:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
|
||||
- src: release/config/sing-box.service
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /usr/lib/systemd/system/sing-box@.service
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
- src: release/completions/sing-box.fish
|
||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||
- src: release/completions/sing-box.zsh
|
||||
dst: /usr/share/zsh/site-functions/_sing-box
|
||||
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
deb:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
version: 2
|
||||
project_name: sing-box
|
||||
builds:
|
||||
- &template
|
||||
@@ -25,13 +26,13 @@ builds:
|
||||
targets:
|
||||
- linux_386
|
||||
- linux_amd64_v1
|
||||
- linux_amd64_v3
|
||||
- linux_arm64
|
||||
- linux_arm_6
|
||||
- linux_arm_7
|
||||
- linux_s390x
|
||||
- linux_riscv64
|
||||
- linux_mips64le
|
||||
- windows_amd64_v1
|
||||
- windows_amd64_v3
|
||||
- windows_386
|
||||
- windows_arm64
|
||||
- darwin_amd64_v1
|
||||
@@ -88,8 +89,6 @@ builds:
|
||||
- android_arm64
|
||||
- android_386
|
||||
- android_amd64
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
archives:
|
||||
- &template
|
||||
id: archive
|
||||
@@ -103,7 +102,7 @@ archives:
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
- id: archive-legacy
|
||||
<<: *template
|
||||
builds:
|
||||
@@ -112,7 +111,7 @@ archives:
|
||||
nfpms:
|
||||
- id: package
|
||||
package_name: sing-box
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
builds:
|
||||
- main
|
||||
homepage: https://sing-box.sagernet.org/
|
||||
@@ -123,15 +122,26 @@ nfpms:
|
||||
- deb
|
||||
- rpm
|
||||
- archlinux
|
||||
# - apk
|
||||
# - ipk
|
||||
priority: extra
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
|
||||
- src: release/config/sing-box.service
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /usr/lib/systemd/system/sing-box@.service
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
- src: release/completions/sing-box.fish
|
||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||
- src: release/completions/sing-box.zsh
|
||||
dst: /usr/share/zsh/site-functions/_sing-box
|
||||
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
deb:
|
||||
@@ -143,13 +153,34 @@ nfpms:
|
||||
signature:
|
||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||
overrides:
|
||||
deb:
|
||||
conflicts:
|
||||
- sing-box-beta
|
||||
rpm:
|
||||
conflicts:
|
||||
- sing-box-beta
|
||||
apk:
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
|
||||
- src: release/config/sing-box.initd
|
||||
dst: /etc/init.d/sing-box
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
- src: release/completions/sing-box.fish
|
||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||
- src: release/completions/sing-box.zsh
|
||||
dst: /usr/share/zsh/site-functions/_sing-box
|
||||
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
ipk:
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
|
||||
- src: release/config/openwrt.init
|
||||
dst: /etc/init.d/sing-box
|
||||
- src: release/config/openwrt.conf
|
||||
dst: /etc/config/sing-box
|
||||
source:
|
||||
enabled: false
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||
|
||||
49
Makefile
49
Makefile
@@ -27,6 +27,9 @@ ci_build:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
generate_completions:
|
||||
go run -v --tags generate,generate_completions $(MAIN)
|
||||
|
||||
install:
|
||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
@@ -66,7 +69,6 @@ release:
|
||||
dist/*.deb \
|
||||
dist/*.rpm \
|
||||
dist/*_amd64.pkg.tar.zst \
|
||||
dist/*_amd64v3.pkg.tar.zst \
|
||||
dist/*_arm64.pkg.tar.zst \
|
||||
dist/release
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||
@@ -125,29 +127,44 @@ release_macos: build_macos upload_macos_app_store
|
||||
|
||||
build_macos_standalone:
|
||||
cd ../sing-box-for-apple && \
|
||||
rm -rf build/SFT.System.xcarchive && \
|
||||
rm -rf build/SFM.System.xcarchive && \
|
||||
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
|
||||
|
||||
notarize_macos_standalone:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
wait_notarize_macos_standalone:
|
||||
sleep 60
|
||||
|
||||
export_macos_standalone:
|
||||
build_macos_dmg:
|
||||
rm -rf dist/SFM
|
||||
mkdir -p dist/SFM
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM"
|
||||
rm -rf build/SFM.System && \
|
||||
rm -rf build/SFM.dmg && \
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "build/SFM.System.xcarchive" \
|
||||
-exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \
|
||||
-exportPath "build/SFM.System" && \
|
||||
create-dmg \
|
||||
--volname "sing-box" \
|
||||
--volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||
--icon "SFM.app" 0 0 \
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
--notarize "notarytool-password" \
|
||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||
|
||||
upload_macos_standalone:
|
||||
upload_macos_dmg:
|
||||
cd dist/SFM && \
|
||||
rm -f *.zip && \
|
||||
zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" *.zip
|
||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||
|
||||
release_macos_standalone: build_macos_standalone notarize_macos_standalone wait_notarize_macos_standalone export_macos_standalone upload_macos_standalone
|
||||
upload_macos_dsyms:
|
||||
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
||||
zip -r SFM.dSYMs.zip dSYMs && \
|
||||
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
||||
popd && \
|
||||
cd dist/SFM && \
|
||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
|
||||
build_tvos:
|
||||
cd ../sing-box-for-apple && \
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ConnectionRouter interface {
|
||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
}
|
||||
|
||||
func NewRouteHandler(
|
||||
metadata InboundContext,
|
||||
router ConnectionRouter,
|
||||
logger logger.ContextLogger,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &routeHandlerWrapper{
|
||||
metadata: metadata,
|
||||
router: router,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRouteContextHandler(
|
||||
router ConnectionRouter,
|
||||
logger logger.ContextLogger,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &routeContextHandlerWrapper{
|
||||
router: router,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||
|
||||
type routeHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||
|
||||
type routeContextHandlerWrapper struct {
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
@@ -6,27 +6,53 @@ import (
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
type ConnectionHandler interface {
|
||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type ConnectionHandlerEx interface {
|
||||
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
// Deprecated: use PacketHandlerEx instead
|
||||
type PacketHandler interface {
|
||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type PacketHandlerEx interface {
|
||||
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
|
||||
}
|
||||
|
||||
// Deprecated: use OOBPacketHandlerEx instead
|
||||
type OOBPacketHandler interface {
|
||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type OOBPacketHandlerEx interface {
|
||||
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
type PacketConnectionHandler interface {
|
||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type PacketConnectionHandlerEx interface {
|
||||
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
type UpstreamHandlerAdapter interface {
|
||||
N.TCPConnectionHandler
|
||||
N.UDPConnectionHandler
|
||||
E.Handler
|
||||
}
|
||||
|
||||
type UpstreamHandlerAdapterEx interface {
|
||||
N.TCPConnectionHandlerEx
|
||||
N.UDPConnectionHandlerEx
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Inbound interface {
|
||||
@@ -17,11 +16,19 @@ type Inbound interface {
|
||||
Tag() string
|
||||
}
|
||||
|
||||
type InjectableInbound interface {
|
||||
type TCPInjectableInbound interface {
|
||||
Inbound
|
||||
Network() []string
|
||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
ConnectionHandlerEx
|
||||
}
|
||||
|
||||
type UDPInjectableInbound interface {
|
||||
Inbound
|
||||
PacketConnectionHandlerEx
|
||||
}
|
||||
|
||||
type InboundRegistry interface {
|
||||
option.InboundOptionsRegistry
|
||||
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
|
||||
}
|
||||
|
||||
type InboundContext struct {
|
||||
@@ -43,10 +50,15 @@ type InboundContext struct {
|
||||
|
||||
// cache
|
||||
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
InboundOptions option.InboundOptions
|
||||
// Deprecated: implement in rule action
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
// Deprecated
|
||||
InboundOptions option.InboundOptions
|
||||
UDPDisableDomainUnmapping bool
|
||||
DNSServer string
|
||||
|
||||
DestinationAddresses []netip.Addr
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
@@ -91,15 +103,6 @@ func ContextFrom(ctx context.Context) *InboundContext {
|
||||
return metadata.(*InboundContext)
|
||||
}
|
||||
|
||||
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||
metadata := ContextFrom(ctx)
|
||||
if metadata != nil {
|
||||
return ctx, metadata
|
||||
}
|
||||
metadata = new(InboundContext)
|
||||
return WithContext(ctx, metadata), metadata
|
||||
}
|
||||
|
||||
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||
var newMetadata InboundContext
|
||||
if metadata := ContextFrom(ctx); metadata != nil {
|
||||
|
||||
21
adapter/inbound/adapter.go
Normal file
21
adapter/inbound/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package inbound
|
||||
|
||||
type Adapter struct {
|
||||
inboundType string
|
||||
inboundTag string
|
||||
}
|
||||
|
||||
func NewAdapter(inboundType string, inboundTag string) Adapter {
|
||||
return Adapter{
|
||||
inboundType: inboundType,
|
||||
inboundTag: inboundTag,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.inboundType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.inboundTag
|
||||
}
|
||||
68
adapter/inbound/registry.go
Normal file
68
adapter/inbound/registry.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
|
||||
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
})
|
||||
}
|
||||
|
||||
var _ adapter.InboundRegistry = (*Registry)(nil)
|
||||
|
||||
type (
|
||||
optionsConstructorFunc func() any
|
||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructors map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructors: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
constructor, loaded := r.constructors[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, options)
|
||||
}
|
||||
|
||||
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
r.optionsType[outboundType] = optionsConstructor
|
||||
r.constructors[outboundType] = constructor
|
||||
}
|
||||
@@ -2,8 +2,9 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,9 @@ type Outbound interface {
|
||||
Network() []string
|
||||
Dependencies() []string
|
||||
N.Dialer
|
||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type OutboundRegistry interface {
|
||||
option.OutboundOptionsRegistry
|
||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||
}
|
||||
|
||||
45
adapter/outbound/adapter.go
Normal file
45
adapter/outbound/adapter.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
protocol string
|
||||
network []string
|
||||
tag string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
protocol: protocol,
|
||||
network: network,
|
||||
tag: tag,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(protocol, network, tag, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.protocol
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.tag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
return a.network
|
||||
}
|
||||
|
||||
func (a *Adapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
@@ -21,42 +19,6 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type myOutboundAdapter struct {
|
||||
protocol string
|
||||
network []string
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
tag string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func (a *myOutboundAdapter) Type() string {
|
||||
return a.protocol
|
||||
}
|
||||
|
||||
func (a *myOutboundAdapter) Tag() string {
|
||||
return a.tag
|
||||
}
|
||||
|
||||
func (a *myOutboundAdapter) Network() []string {
|
||||
return a.network
|
||||
}
|
||||
|
||||
func (a *myOutboundAdapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
|
||||
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
|
||||
NewError(a.logger, ctx, err)
|
||||
}
|
||||
|
||||
func withDialerDependency(options option.DialerOptions) []string {
|
||||
if options.Detour != "" {
|
||||
return []string{options.Detour}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var outConn net.Conn
|
||||
@@ -69,7 +31,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportHandshakeSuccess(conn)
|
||||
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
@@ -96,7 +58,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportHandshakeSuccess(conn)
|
||||
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
@@ -117,14 +79,14 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportHandshakeSuccess(conn)
|
||||
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
if metadata.Destination.IsFqdn() {
|
||||
if metadata.InboundOptions.UDPDisableDomainUnmapping {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
} else {
|
||||
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
@@ -165,7 +127,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportHandshakeSuccess(conn)
|
||||
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
@@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
|
||||
}
|
||||
return bufio.CopyConn(ctx, conn, serverConn)
|
||||
}
|
||||
|
||||
func NewError(logger log.ContextLogger, ctx context.Context, err error) {
|
||||
common.Close(err)
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
logger.DebugContext(ctx, "connection closed: ", err)
|
||||
return
|
||||
}
|
||||
logger.ErrorContext(ctx, err)
|
||||
}
|
||||
68
adapter/outbound/registry.go
Normal file
68
adapter/outbound/registry.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
|
||||
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
})
|
||||
}
|
||||
|
||||
var _ adapter.OutboundRegistry = (*Registry)(nil)
|
||||
|
||||
type (
|
||||
optionsConstructorFunc func() any
|
||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructors map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructors: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
constructor, loaded := r.constructors[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, options)
|
||||
}
|
||||
|
||||
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
r.optionsType[outboundType] = optionsConstructor
|
||||
r.constructors[outboundType] = constructor
|
||||
}
|
||||
@@ -2,13 +2,17 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -30,6 +34,8 @@ type Router interface {
|
||||
FakeIPStore() FakeIPStore
|
||||
|
||||
ConnectionRouter
|
||||
PreMatch(metadata InboundContext) error
|
||||
ConnectionRouterEx
|
||||
|
||||
GeoIPReader() *geoip.Reader
|
||||
LoadGeosite(code string) (Rule, error)
|
||||
@@ -66,6 +72,18 @@ type Router interface {
|
||||
ResetNetwork() error
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
type ConnectionRouter interface {
|
||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type ConnectionRouterEx interface {
|
||||
ConnectionRouter
|
||||
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||
return service.ContextWith(ctx, router)
|
||||
}
|
||||
@@ -74,31 +92,9 @@ func RouterFromContext(ctx context.Context) Router {
|
||||
return service.FromContext[Router](ctx)
|
||||
}
|
||||
|
||||
type HeadlessRule interface {
|
||||
Match(metadata *InboundContext) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
HeadlessRule
|
||||
Service
|
||||
Type() string
|
||||
UpdateGeosite() error
|
||||
Outbound() string
|
||||
}
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
DisableCache() bool
|
||||
RewriteTTL() *uint32
|
||||
ClientSubnet() *netip.Prefix
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
}
|
||||
|
||||
type RuleSet interface {
|
||||
Name() string
|
||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||
PostStart() error
|
||||
Metadata() RuleSetMetadata
|
||||
ExtractIPSet() []*netipx.IPSet
|
||||
@@ -118,10 +114,42 @@ type RuleSetMetadata struct {
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
}
|
||||
type HTTPStartContext struct {
|
||||
access sync.Mutex
|
||||
httpClientCache map[string]*http.Client
|
||||
}
|
||||
|
||||
type RuleSetStartContext interface {
|
||||
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
||||
Close()
|
||||
func NewHTTPStartContext() *HTTPStartContext {
|
||||
return &HTTPStartContext{
|
||||
httpClientCache: make(map[string]*http.Client),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
||||
return httpClient
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
TLSHandshakeTimeout: C.TCPTimeout,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
},
|
||||
}
|
||||
c.httpClientCache[detour] = httpClient
|
||||
return httpClient
|
||||
}
|
||||
|
||||
func (c *HTTPStartContext) Close() {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
for _, client := range c.httpClientCache {
|
||||
client.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
|
||||
38
adapter/rule.go
Normal file
38
adapter/rule.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
type HeadlessRule interface {
|
||||
Match(metadata *InboundContext) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
HeadlessRule
|
||||
Service
|
||||
Type() string
|
||||
UpdateGeosite() error
|
||||
Action() RuleAction
|
||||
}
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
}
|
||||
|
||||
type RuleAction interface {
|
||||
Type() string
|
||||
String() string
|
||||
}
|
||||
|
||||
func IsFinalAction(action RuleAction) bool {
|
||||
switch action.Type() {
|
||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -4,112 +4,165 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type (
|
||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
)
|
||||
|
||||
func NewUpstreamHandler(
|
||||
func NewUpstreamHandlerEx(
|
||||
metadata InboundContext,
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
errorHandler E.Handler,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &myUpstreamHandlerWrapper{
|
||||
connectionHandler ConnectionHandlerFuncEx,
|
||||
packetHandler PacketConnectionHandlerFuncEx,
|
||||
) UpstreamHandlerAdapterEx {
|
||||
return &myUpstreamHandlerWrapperEx{
|
||||
metadata: metadata,
|
||||
connectionHandler: connectionHandler,
|
||||
packetHandler: packetHandler,
|
||||
errorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
||||
|
||||
type myUpstreamHandlerWrapper struct {
|
||||
type myUpstreamHandlerWrapperEx struct {
|
||||
metadata InboundContext
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
connectionHandler ConnectionHandlerFuncEx
|
||||
packetHandler PacketConnectionHandlerFuncEx
|
||||
}
|
||||
|
||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
if source.IsValid() {
|
||||
myMetadata.Source = source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
if destination.IsValid() {
|
||||
myMetadata.Destination = destination
|
||||
}
|
||||
return w.connectionHandler(ctx, conn, myMetadata)
|
||||
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
||||
}
|
||||
|
||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
if source.IsValid() {
|
||||
myMetadata.Source = source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
if destination.IsValid() {
|
||||
myMetadata.Destination = destination
|
||||
}
|
||||
return w.packetHandler(ctx, conn, myMetadata)
|
||||
w.packetHandler(ctx, conn, myMetadata, onClose)
|
||||
}
|
||||
|
||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
||||
|
||||
type myUpstreamContextHandlerWrapperEx struct {
|
||||
connectionHandler ConnectionHandlerFuncEx
|
||||
packetHandler PacketConnectionHandlerFuncEx
|
||||
}
|
||||
|
||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
return M.Metadata{
|
||||
Source: metadata.Source,
|
||||
Destination: metadata.Destination,
|
||||
}
|
||||
}
|
||||
|
||||
type myUpstreamContextHandlerWrapper struct {
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
func NewUpstreamContextHandler(
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
errorHandler E.Handler,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &myUpstreamContextHandlerWrapper{
|
||||
func NewUpstreamContextHandlerEx(
|
||||
connectionHandler ConnectionHandlerFuncEx,
|
||||
packetHandler PacketConnectionHandlerFuncEx,
|
||||
) UpstreamHandlerAdapterEx {
|
||||
return &myUpstreamContextHandlerWrapperEx{
|
||||
connectionHandler: connectionHandler,
|
||||
packetHandler: packetHandler,
|
||||
errorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
if source.IsValid() {
|
||||
myMetadata.Source = source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
if destination.IsValid() {
|
||||
myMetadata.Destination = destination
|
||||
}
|
||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
||||
}
|
||||
|
||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
if source.IsValid() {
|
||||
myMetadata.Source = source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
if destination.IsValid() {
|
||||
myMetadata.Destination = destination
|
||||
}
|
||||
return w.packetHandler(ctx, conn, *myMetadata)
|
||||
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
||||
}
|
||||
|
||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
func NewRouteHandlerEx(
|
||||
metadata InboundContext,
|
||||
router ConnectionRouterEx,
|
||||
) UpstreamHandlerAdapterEx {
|
||||
return &routeHandlerWrapperEx{
|
||||
metadata: metadata,
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
|
||||
|
||||
type routeHandlerWrapperEx struct {
|
||||
metadata InboundContext
|
||||
router ConnectionRouterEx
|
||||
}
|
||||
|
||||
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
if source.IsValid() {
|
||||
r.metadata.Source = source
|
||||
}
|
||||
if destination.IsValid() {
|
||||
r.metadata.Destination = destination
|
||||
}
|
||||
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
|
||||
}
|
||||
|
||||
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
if source.IsValid() {
|
||||
r.metadata.Source = source
|
||||
}
|
||||
if destination.IsValid() {
|
||||
r.metadata.Destination = destination
|
||||
}
|
||||
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
|
||||
}
|
||||
|
||||
func NewRouteContextHandlerEx(
|
||||
router ConnectionRouterEx,
|
||||
) UpstreamHandlerAdapterEx {
|
||||
return &routeContextHandlerWrapperEx{
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
|
||||
|
||||
type routeContextHandlerWrapperEx struct {
|
||||
router ConnectionRouterEx
|
||||
}
|
||||
|
||||
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
metadata := ContextFrom(ctx)
|
||||
if source.IsValid() {
|
||||
metadata.Source = source
|
||||
}
|
||||
if destination.IsValid() {
|
||||
metadata.Destination = destination
|
||||
}
|
||||
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
|
||||
}
|
||||
|
||||
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
metadata := ContextFrom(ctx)
|
||||
if source.IsValid() {
|
||||
metadata.Source = source
|
||||
}
|
||||
if destination.IsValid() {
|
||||
metadata.Destination = destination
|
||||
}
|
||||
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
|
||||
}
|
||||
|
||||
216
adapter/upstream_legacy.go
Normal file
216
adapter/upstream_legacy.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type (
|
||||
// Deprecated
|
||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
// Deprecated
|
||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
func NewUpstreamHandler(
|
||||
metadata InboundContext,
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
errorHandler E.Handler,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &myUpstreamHandlerWrapper{
|
||||
metadata: metadata,
|
||||
connectionHandler: connectionHandler,
|
||||
packetHandler: packetHandler,
|
||||
errorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated
|
||||
type myUpstreamHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.connectionHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.packetHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
return M.Metadata{
|
||||
Source: metadata.Source,
|
||||
Destination: metadata.Destination,
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
type myUpstreamContextHandlerWrapper struct {
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func NewUpstreamContextHandler(
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
errorHandler E.Handler,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &myUpstreamContextHandlerWrapper{
|
||||
connectionHandler: connectionHandler,
|
||||
packetHandler: packetHandler,
|
||||
errorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.packetHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func NewRouteHandler(
|
||||
metadata InboundContext,
|
||||
router ConnectionRouter,
|
||||
logger logger.ContextLogger,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &routeHandlerWrapper{
|
||||
metadata: metadata,
|
||||
router: router,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func NewRouteContextHandler(
|
||||
router ConnectionRouter,
|
||||
logger logger.ContextLogger,
|
||||
) UpstreamHandlerAdapter {
|
||||
return &routeContextHandlerWrapper{
|
||||
router: router,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
type routeHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
type routeContextHandlerWrapper struct {
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
myMetadata.Source = metadata.Source
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
myMetadata.Destination = metadata.Destination
|
||||
}
|
||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -16,8 +15,7 @@ type V2RayServerTransport interface {
|
||||
}
|
||||
|
||||
type V2RayServerTransportHandler interface {
|
||||
N.TCPConnectionHandler
|
||||
E.Handler
|
||||
N.TCPConnectionHandlerEx
|
||||
}
|
||||
|
||||
type V2RayClientTransport interface {
|
||||
|
||||
91
box.go
91
box.go
@@ -14,10 +14,9 @@ import (
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/inbound"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/direct"
|
||||
"github.com/sagernet/sing-box/route"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -44,16 +43,37 @@ type Box struct {
|
||||
type Options struct {
|
||||
option.Options
|
||||
Context context.Context
|
||||
PlatformInterface platform.Interface
|
||||
PlatformLogWriter log.PlatformWriter
|
||||
}
|
||||
|
||||
func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context {
|
||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||
}
|
||||
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func New(options Options) (*Box, error) {
|
||||
createdAt := time.Now()
|
||||
ctx := options.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
if inboundRegistry == nil {
|
||||
return nil, E.New("missing inbound registry in context")
|
||||
}
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
if outboundRegistry == nil {
|
||||
return nil, E.New("missing outbound registry in context")
|
||||
}
|
||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||
ctx = pause.WithDefaultManager(ctx)
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
@@ -70,8 +90,9 @@ func New(options Options) (*Box, error) {
|
||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var defaultLogWriter io.Writer
|
||||
if options.PlatformInterface != nil {
|
||||
if platformInterface != nil {
|
||||
defaultLogWriter = io.Discard
|
||||
}
|
||||
logFactory, err := log.New(log.Options{
|
||||
@@ -92,64 +113,92 @@ func New(options Options) (*Box, error) {
|
||||
common.PtrValueOrDefault(options.DNS),
|
||||
common.PtrValueOrDefault(options.NTP),
|
||||
options.Inbounds,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse route options")
|
||||
}
|
||||
//nolint:staticcheck
|
||||
if len(options.LegacyInbounds) > 0 {
|
||||
for _, legacyInbound := range options.LegacyInbounds {
|
||||
options.Inbounds = append(options.Inbounds, option.Inbound{
|
||||
Type: legacyInbound.Type,
|
||||
Tag: legacyInbound.Tag,
|
||||
Options: common.Must1(legacyInbound.RawOptions()),
|
||||
})
|
||||
}
|
||||
}
|
||||
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||
//nolint:staticcheck
|
||||
if len(options.LegacyOutbounds) > 0 {
|
||||
for _, legacyOutbound := range options.LegacyOutbounds {
|
||||
options.Outbounds = append(options.Outbounds, option.Outbound{
|
||||
Type: legacyOutbound.Type,
|
||||
Tag: legacyOutbound.Tag,
|
||||
Options: common.Must1(legacyOutbound.RawOptions()),
|
||||
})
|
||||
}
|
||||
}
|
||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
var in adapter.Inbound
|
||||
var currentInbound adapter.Inbound
|
||||
var tag string
|
||||
if inboundOptions.Tag != "" {
|
||||
tag = inboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
in, err = inbound.New(
|
||||
currentInbound, err = inboundRegistry.CreateInbound(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
inboundOptions,
|
||||
options.PlatformInterface,
|
||||
inboundOptions.Type,
|
||||
inboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||
}
|
||||
inbounds = append(inbounds, in)
|
||||
inbounds = append(inbounds, currentInbound)
|
||||
}
|
||||
for i, outboundOptions := range options.Outbounds {
|
||||
var out adapter.Outbound
|
||||
var currentOutbound adapter.Outbound
|
||||
var tag string
|
||||
if outboundOptions.Tag != "" {
|
||||
tag = outboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
out, err = outbound.New(
|
||||
ctx,
|
||||
outboundCtx := ctx
|
||||
if tag != "" {
|
||||
// TODO: remove this
|
||||
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||
Outbound: tag,
|
||||
})
|
||||
}
|
||||
currentOutbound, err = outboundRegistry.CreateOutbound(
|
||||
outboundCtx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
outboundOptions)
|
||||
outboundOptions.Type,
|
||||
outboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||
}
|
||||
outbounds = append(outbounds, out)
|
||||
outbounds = append(outbounds, currentOutbound)
|
||||
}
|
||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
||||
common.Must(oErr)
|
||||
outbounds = append(outbounds, out)
|
||||
return out
|
||||
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
|
||||
common.Must(cErr)
|
||||
outbounds = append(outbounds, defaultOutbound)
|
||||
return defaultOutbound
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.PlatformInterface != nil {
|
||||
err = options.PlatformInterface.Initialize(ctx, router)
|
||||
if platformInterface != nil {
|
||||
err = platformInterface.Initialize(ctx, router)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize platform interface")
|
||||
}
|
||||
|
||||
Submodule clients/android updated: 440aaa9a1a...45a1f5f0aa
Submodule clients/apple updated: aa4ce98421...c7d9b49de7
@@ -58,7 +58,7 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "26.2.11394342"
|
||||
const fixedVersion = "27.2.12479018"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
@@ -86,7 +86,7 @@ func findNDK() bool {
|
||||
})
|
||||
for _, versionName := range versionNames {
|
||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
|
||||
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
|
||||
androidNDKPath = currentNDKPath
|
||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||
return true
|
||||
|
||||
@@ -27,7 +27,7 @@ func main() {
|
||||
objectsMap := project["objects"].(map[string]any)
|
||||
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
||||
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
|
||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.standalone", "io.nekohasekai.sfa.system"}, newVersion.String())
|
||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
|
||||
if updated0 || updated1 {
|
||||
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||
}
|
||||
|
||||
73
cmd/sing-box/cmd.go
Normal file
73
cmd/sing-box/cmd.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
globalCtx context.Context
|
||||
configPaths []string
|
||||
configDirectories []string
|
||||
workingDir string
|
||||
disableColor bool
|
||||
)
|
||||
|
||||
var mainCommand = &cobra.Command{
|
||||
Use: "sing-box",
|
||||
PersistentPreRun: preRun,
|
||||
}
|
||||
|
||||
func init() {
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||
}
|
||||
|
||||
func preRun(cmd *cobra.Command, args []string) {
|
||||
globalCtx = context.Background()
|
||||
sudoUser := os.Getenv("SUDO_USER")
|
||||
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
|
||||
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
|
||||
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
|
||||
sudoUserObject, _ := user.Lookup(sudoUser)
|
||||
if sudoUserObject != nil {
|
||||
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
|
||||
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
|
||||
}
|
||||
}
|
||||
if sudoUID > 0 && sudoGID > 0 {
|
||||
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
|
||||
}
|
||||
if disableColor {
|
||||
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
|
||||
}
|
||||
if workingDir != "" {
|
||||
_, err := os.Stat(workingDir)
|
||||
if err != nil {
|
||||
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
|
||||
}
|
||||
err = os.Chdir(workingDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -38,7 +39,7 @@ func format() error {
|
||||
return err
|
||||
}
|
||||
for _, optionsEntry := range optionsList {
|
||||
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
||||
optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -68,29 +68,19 @@ func merge(outputPath string) error {
|
||||
}
|
||||
|
||||
func mergePathResources(options *option.Options) error {
|
||||
for index, inbound := range options.Inbounds {
|
||||
rawOptions, err := inbound.RawOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||
for _, inbound := range options.Inbounds {
|
||||
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
||||
}
|
||||
options.Inbounds[index] = inbound
|
||||
}
|
||||
for index, outbound := range options.Outbounds {
|
||||
rawOptions, err := outbound.RawOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, outbound := range options.Outbounds {
|
||||
switch outbound.Type {
|
||||
case C.TypeSSH:
|
||||
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
||||
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
||||
}
|
||||
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||
}
|
||||
options.Outbounds[index] = outbound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
||||
return options
|
||||
}
|
||||
|
||||
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
||||
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
|
||||
if options.PrivateKeyPath != "" {
|
||||
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func trimStringArray(array []string) []string {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
}
|
||||
for i, ruleOptions := range plainRuleSet.Rules {
|
||||
var currentRule adapter.HeadlessRule
|
||||
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
||||
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read config at ", path)
|
||||
}
|
||||
options, err := json.UnmarshalExtended[option.Options](configContent)
|
||||
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode config at ", path)
|
||||
}
|
||||
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
|
||||
}
|
||||
var mergedMessage json.RawMessage
|
||||
for _, options := range optionsList {
|
||||
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
|
||||
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||
}
|
||||
}
|
||||
var mergedOptions option.Options
|
||||
err = mergedOptions.UnmarshalJSON(mergedMessage)
|
||||
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -23,7 +26,9 @@ func init() {
|
||||
func createPreStartedClient() (*box.Box, error) {
|
||||
options, err := readConfigAndMerge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
instance, err := box.New(box.Options{Options: options})
|
||||
if err != nil {
|
||||
|
||||
28
cmd/sing-box/generate_completions.go
Normal file
28
cmd/sing-box/generate_completions.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build generate && generate_completions
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/sagernet/sing-box/log"
|
||||
|
||||
func main() {
|
||||
err := generateCompletions()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateCompletions() error {
|
||||
err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/route"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -26,7 +26,7 @@ example.arpa
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"example.org",
|
||||
@@ -85,7 +85,7 @@ func TestHosts(t *testing.T) {
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"google.com",
|
||||
@@ -115,7 +115,7 @@ www.example.org
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"example.com",
|
||||
|
||||
@@ -1,74 +1,11 @@
|
||||
//go:build !generate
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
_ "github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
globalCtx context.Context
|
||||
configPaths []string
|
||||
configDirectories []string
|
||||
workingDir string
|
||||
disableColor bool
|
||||
)
|
||||
|
||||
var mainCommand = &cobra.Command{
|
||||
Use: "sing-box",
|
||||
PersistentPreRun: preRun,
|
||||
}
|
||||
|
||||
func init() {
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||
}
|
||||
import "github.com/sagernet/sing-box/log"
|
||||
|
||||
func main() {
|
||||
if err := mainCommand.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func preRun(cmd *cobra.Command, args []string) {
|
||||
globalCtx = context.Background()
|
||||
sudoUser := os.Getenv("SUDO_USER")
|
||||
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
|
||||
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
|
||||
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
|
||||
sudoUserObject, _ := user.Lookup(sudoUser)
|
||||
if sudoUserObject != nil {
|
||||
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
|
||||
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
|
||||
}
|
||||
}
|
||||
if sudoUID > 0 && sudoGID > 0 {
|
||||
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
|
||||
}
|
||||
if disableColor {
|
||||
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
|
||||
}
|
||||
if workingDir != "" {
|
||||
_, err := os.Stat(workingDir)
|
||||
if err != nil {
|
||||
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
|
||||
}
|
||||
err = os.Chdir(workingDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
||||
if options.ConnectTimeout != 0 {
|
||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||
} else {
|
||||
dialer.Timeout = C.TCPTimeout
|
||||
dialer.Timeout = C.TCPConnectTimeout
|
||||
}
|
||||
// TODO: Add an option to customize the keep alive period
|
||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||
@@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
||||
setMultiPathTCP(&dialer4)
|
||||
}
|
||||
if options.IsWireGuardListener {
|
||||
for _, controlFn := range wgControlFns {
|
||||
for _, controlFn := range WgControlFns {
|
||||
listener.Control = control.Append(listener.Control, controlFn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@ package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
|
||||
type WireGuardListener interface {
|
||||
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
var WgControlFns []control.Func
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build with_wireguard
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
)
|
||||
|
||||
var _ WireGuardListener = (conn.Listener)(nil)
|
||||
|
||||
var wgControlFns = conn.ControlFns
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build !with_wireguard
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
|
||||
var wgControlFns []control.Func
|
||||
136
common/listener/listener.go
Normal file
136
common/listener/listener.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package listener
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
network []string
|
||||
listenOptions option.ListenOptions
|
||||
connHandler adapter.ConnectionHandlerEx
|
||||
packetHandler adapter.PacketHandlerEx
|
||||
oobPacketHandler adapter.OOBPacketHandlerEx
|
||||
threadUnsafePacketWriter bool
|
||||
disablePacketOutput bool
|
||||
setSystemProxy bool
|
||||
systemProxySOCKS bool
|
||||
|
||||
tcpListener net.Listener
|
||||
systemProxy settings.SystemProxy
|
||||
udpConn *net.UDPConn
|
||||
udpAddr M.Socksaddr
|
||||
packetOutbound chan *N.PacketBuffer
|
||||
packetOutboundClosed chan struct{}
|
||||
shutdown atomic.Bool
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Context context.Context
|
||||
Logger logger.ContextLogger
|
||||
Network []string
|
||||
Listen option.ListenOptions
|
||||
ConnectionHandler adapter.ConnectionHandlerEx
|
||||
PacketHandler adapter.PacketHandlerEx
|
||||
OOBPacketHandler adapter.OOBPacketHandlerEx
|
||||
ThreadUnsafePacketWriter bool
|
||||
DisablePacketOutput bool
|
||||
SetSystemProxy bool
|
||||
SystemProxySOCKS bool
|
||||
}
|
||||
|
||||
func New(
|
||||
options Options,
|
||||
) *Listener {
|
||||
return &Listener{
|
||||
ctx: options.Context,
|
||||
logger: options.Logger,
|
||||
network: options.Network,
|
||||
listenOptions: options.Listen,
|
||||
connHandler: options.ConnectionHandler,
|
||||
packetHandler: options.PacketHandler,
|
||||
oobPacketHandler: options.OOBPacketHandler,
|
||||
threadUnsafePacketWriter: options.ThreadUnsafePacketWriter,
|
||||
disablePacketOutput: options.DisablePacketOutput,
|
||||
setSystemProxy: options.SetSystemProxy,
|
||||
systemProxySOCKS: options.SystemProxySOCKS,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Start() error {
|
||||
if common.Contains(l.network, N.NetworkTCP) {
|
||||
_, err := l.ListenTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go l.loopTCPIn()
|
||||
}
|
||||
if common.Contains(l.network, N.NetworkUDP) {
|
||||
_, err := l.ListenUDP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.packetOutboundClosed = make(chan struct{})
|
||||
l.packetOutbound = make(chan *N.PacketBuffer, 64)
|
||||
go l.loopUDPIn()
|
||||
if !l.disablePacketOutput {
|
||||
go l.loopUDPOut()
|
||||
}
|
||||
}
|
||||
if l.setSystemProxy {
|
||||
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
||||
var listenAddrString string
|
||||
listenAddr := l.listenOptions.Listen.Build()
|
||||
if listenAddr.IsUnspecified() {
|
||||
listenAddrString = "127.0.0.1"
|
||||
} else {
|
||||
listenAddrString = listenAddr.String()
|
||||
}
|
||||
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize system proxy")
|
||||
}
|
||||
err = systemProxy.Enable()
|
||||
if err != nil {
|
||||
return E.Cause(err, "set system proxy")
|
||||
}
|
||||
l.systemProxy = systemProxy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.shutdown.Store(true)
|
||||
var err error
|
||||
if l.systemProxy != nil && l.systemProxy.IsEnabled() {
|
||||
err = l.systemProxy.Disable()
|
||||
}
|
||||
return E.Errors(err, common.Close(
|
||||
l.tcpListener,
|
||||
common.PtrOrNil(l.udpConn),
|
||||
))
|
||||
}
|
||||
|
||||
func (l *Listener) TCPListener() net.Listener {
|
||||
return l.tcpListener
|
||||
}
|
||||
|
||||
func (l *Listener) UDPConn() *net.UDPConn {
|
||||
return l.udpConn
|
||||
}
|
||||
|
||||
func (l *Listener) ListenOptions() option.ListenOptions {
|
||||
return l.listenOptions
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build go1.21
|
||||
|
||||
package inbound
|
||||
package listener
|
||||
|
||||
import "net"
|
||||
|
||||
16
common/listener/listener_go123.go
Normal file
16
common/listener/listener_go123.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build go1.23
|
||||
|
||||
package listener
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
||||
listener.KeepAliveConfig = net.KeepAliveConfig{
|
||||
Enable: true,
|
||||
Idle: idle,
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package inbound
|
||||
package listener
|
||||
|
||||
import "net"
|
||||
|
||||
15
common/listener/listener_nongo123.go
Normal file
15
common/listener/listener_nongo123.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package listener
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
|
||||
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
||||
listener.KeepAlive = idle
|
||||
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
|
||||
}
|
||||
85
common/listener/listener_tcp.go
Normal file
85
common/listener/listener_tcp.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package listener
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
|
||||
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
var err error
|
||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
|
||||
var tcpListener net.Listener
|
||||
var listenConfig net.ListenConfig
|
||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||
if keepIdle == 0 {
|
||||
keepIdle = C.TCPKeepAliveInitial
|
||||
}
|
||||
keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)
|
||||
if keepInterval == 0 {
|
||||
keepInterval = C.TCPKeepAliveInterval
|
||||
}
|
||||
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
|
||||
}
|
||||
if l.listenOptions.TCPMultiPath {
|
||||
if !go121Available {
|
||||
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
||||
}
|
||||
setMultiPathTCP(&listenConfig)
|
||||
}
|
||||
if l.listenOptions.TCPFastOpen {
|
||||
var tfoConfig tfo.ListenConfig
|
||||
tfoConfig.ListenConfig = listenConfig
|
||||
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||
} else {
|
||||
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||
}
|
||||
if err == nil {
|
||||
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||
}
|
||||
//nolint:staticcheck
|
||||
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
||||
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
||||
}
|
||||
l.tcpListener = tcpListener
|
||||
return tcpListener, err
|
||||
}
|
||||
|
||||
func (l *Listener) loopTCPIn() {
|
||||
tcpListener := l.tcpListener
|
||||
var metadata adapter.InboundContext
|
||||
for {
|
||||
conn, err := tcpListener.Accept()
|
||||
if err != nil {
|
||||
//nolint:staticcheck
|
||||
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
|
||||
l.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
if l.shutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
l.tcpListener.Close()
|
||||
l.logger.Error("tcp listener closed: ", err)
|
||||
continue
|
||||
}
|
||||
//nolint:staticcheck
|
||||
metadata.InboundDetour = l.listenOptions.Detour
|
||||
//nolint:staticcheck
|
||||
metadata.InboundOptions = l.listenOptions.InboundOptions
|
||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||
ctx := log.ContextWithNewID(l.ctx)
|
||||
l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)
|
||||
}
|
||||
}
|
||||
154
common/listener/listener_udp.go
Normal file
154
common/listener/listener_udp.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package listener
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
|
||||
var lc net.ListenConfig
|
||||
var udpFragment bool
|
||||
if l.listenOptions.UDPFragment != nil {
|
||||
udpFragment = *l.listenOptions.UDPFragment
|
||||
} else {
|
||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||
}
|
||||
if !udpFragment {
|
||||
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||
}
|
||||
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.udpConn = udpConn.(*net.UDPConn)
|
||||
l.udpAddr = bindAddr
|
||||
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
||||
return udpConn, err
|
||||
}
|
||||
|
||||
func (l *Listener) UDPAddr() M.Socksaddr {
|
||||
return l.udpAddr
|
||||
}
|
||||
|
||||
func (l *Listener) PacketWriter() N.PacketWriter {
|
||||
return (*packetWriter)(l)
|
||||
}
|
||||
|
||||
func (l *Listener) loopUDPIn() {
|
||||
defer close(l.packetOutboundClosed)
|
||||
var buffer *buf.Buffer
|
||||
if !l.threadUnsafePacketWriter {
|
||||
buffer = buf.NewPacket()
|
||||
defer buffer.Release()
|
||||
}
|
||||
buffer.IncRef()
|
||||
defer buffer.DecRef()
|
||||
if l.oobPacketHandler != nil {
|
||||
oob := make([]byte, 1024)
|
||||
for {
|
||||
if l.threadUnsafePacketWriter {
|
||||
buffer = buf.NewPacket()
|
||||
} else {
|
||||
buffer.Reset()
|
||||
}
|
||||
n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
|
||||
if err != nil {
|
||||
if l.threadUnsafePacketWriter {
|
||||
buffer.Release()
|
||||
}
|
||||
if l.shutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
l.udpConn.Close()
|
||||
l.logger.Error("udp listener closed: ", err)
|
||||
return
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())
|
||||
}
|
||||
} else {
|
||||
for {
|
||||
if l.threadUnsafePacketWriter {
|
||||
buffer = buf.NewPacket()
|
||||
} else {
|
||||
buffer.Reset()
|
||||
}
|
||||
n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
|
||||
if err != nil {
|
||||
if l.threadUnsafePacketWriter {
|
||||
buffer.Release()
|
||||
}
|
||||
if l.shutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
l.udpConn.Close()
|
||||
l.logger.Error("udp listener closed: ", err)
|
||||
return
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) loopUDPOut() {
|
||||
for {
|
||||
select {
|
||||
case packet := <-l.packetOutbound:
|
||||
destination := packet.Destination.AddrPort()
|
||||
_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)
|
||||
packet.Buffer.Release()
|
||||
N.PutPacketBuffer(packet)
|
||||
if err != nil {
|
||||
if l.shutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
l.udpConn.Close()
|
||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
case <-l.packetOutboundClosed:
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case packet := <-l.packetOutbound:
|
||||
packet.Buffer.Release()
|
||||
N.PutPacketBuffer(packet)
|
||||
case <-time.After(time.Second):
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type packetWriter Listener
|
||||
|
||||
func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
packet := N.NewPacketBuffer()
|
||||
packet.Buffer = buffer
|
||||
packet.Destination = destination
|
||||
select {
|
||||
case w.packetOutbound <- packet:
|
||||
return nil
|
||||
default:
|
||||
buffer.Release()
|
||||
N.PutPacketBuffer(packet)
|
||||
if w.shutdown.Load() {
|
||||
return os.ErrClosed
|
||||
}
|
||||
w.logger.Trace("dropped packet to ", destination)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *packetWriter) WriteIsThreadUnsafe() {
|
||||
}
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
router adapter.ConnectionRouter
|
||||
router adapter.ConnectionRouterEx
|
||||
service *mux.Service
|
||||
}
|
||||
|
||||
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
||||
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
|
||||
if !options.Enabled {
|
||||
return router, nil
|
||||
}
|
||||
@@ -54,6 +54,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context
|
||||
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination == mux.Destination {
|
||||
// TODO: check if WithContext is necessary
|
||||
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||
} else {
|
||||
return r.router.RouteConnection(ctx, conn, metadata)
|
||||
@@ -63,3 +64,15 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
if metadata.Destination == mux.Destination {
|
||||
r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)
|
||||
return
|
||||
}
|
||||
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type V2RayLegacyRouter struct {
|
||||
router adapter.ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
|
||||
return &V2RayLegacyRouter{router, logger}
|
||||
}
|
||||
|
||||
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
|
||||
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
||||
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
|
||||
}
|
||||
return r.router.RouteConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ type (
|
||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||
)
|
||||
|
||||
func Skip(metadata adapter.InboundContext) bool {
|
||||
func Skip(metadata *adapter.InboundContext) bool {
|
||||
// skip server first protocols
|
||||
switch metadata.Destination.Port {
|
||||
case 25, 465, 587:
|
||||
@@ -40,21 +40,27 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
||||
}
|
||||
deadline := time.Now().Add(timeout)
|
||||
var errors []error
|
||||
err := conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set read deadline")
|
||||
}
|
||||
defer conn.SetReadDeadline(time.Time{})
|
||||
for _, sniffer := range sniffers {
|
||||
if buffer.IsEmpty() {
|
||||
err = sniffer(ctx, metadata, io.TeeReader(conn, buffer))
|
||||
} else {
|
||||
err = sniffer(ctx, metadata, io.MultiReader(bytes.NewReader(buffer.Bytes()), io.TeeReader(conn, buffer)))
|
||||
for i := 0; ; i++ {
|
||||
err := conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set read deadline")
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
_, err = buffer.ReadOnceFrom(conn)
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
if i > 0 {
|
||||
break
|
||||
}
|
||||
return E.Cause(err, "read payload")
|
||||
}
|
||||
errors = nil
|
||||
for _, sniffer := range sniffers {
|
||||
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return E.Errors(errors...)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
ruleItemWIFISSID
|
||||
ruleItemWIFIBSSID
|
||||
ruleItemAdGuardDomain
|
||||
ruleItemProcessPathRegex
|
||||
ruleItemFinal uint8 = 0xFF
|
||||
)
|
||||
|
||||
@@ -207,6 +208,8 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
||||
rule.ProcessName, err = readRuleItemString(reader)
|
||||
case ruleItemProcessPath:
|
||||
rule.ProcessPath, err = readRuleItemString(reader)
|
||||
case ruleItemProcessPathRegex:
|
||||
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
||||
case ruleItemPackageName:
|
||||
rule.PackageName, err = readRuleItemString(reader)
|
||||
case ruleItemWIFISSID:
|
||||
@@ -326,6 +329,12 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rule.ProcessPathRegex) > 0 {
|
||||
err = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rule.PackageName) > 0 {
|
||||
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
|
||||
if err != nil {
|
||||
|
||||
@@ -217,18 +217,10 @@ func init() {
|
||||
|
||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||
switch name {
|
||||
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||
fallthrough
|
||||
case "chrome", "":
|
||||
return utls.HelloChrome_Auto, nil
|
||||
case "chrome_psk":
|
||||
return utls.HelloChrome_100_PSK, nil
|
||||
case "chrome_psk_shuffle":
|
||||
return utls.HelloChrome_112_PSK_Shuf, nil
|
||||
case "chrome_padding_psk_shuffle":
|
||||
return utls.HelloChrome_114_Padding_PSK_Shuf, nil
|
||||
case "chrome_pq":
|
||||
return utls.HelloChrome_115_PQ, nil
|
||||
case "chrome_pq_psk":
|
||||
return utls.HelloChrome_115_PQ_PSK, nil
|
||||
case "firefox":
|
||||
return utls.HelloFirefox_Auto, nil
|
||||
case "edge":
|
||||
|
||||
@@ -13,14 +13,14 @@ import (
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
var _ adapter.ConnectionRouter = (*Router)(nil)
|
||||
var _ adapter.ConnectionRouterEx = (*Router)(nil)
|
||||
|
||||
type Router struct {
|
||||
router adapter.ConnectionRouter
|
||||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
||||
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {
|
||||
return &Router{router, logger}
|
||||
}
|
||||
|
||||
@@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
switch metadata.Destination.Fqdn {
|
||||
case uot.MagicAddress:
|
||||
request, err := uot.ReadRequest(conn)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "UoT read request")
|
||||
r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
return
|
||||
}
|
||||
if request.IsConnect {
|
||||
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
|
||||
} else {
|
||||
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
|
||||
}
|
||||
metadata.Domain = metadata.Destination.Fqdn
|
||||
metadata.Destination = request.Destination
|
||||
r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)
|
||||
return
|
||||
case uot.LegacyMagicAddress:
|
||||
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
|
||||
metadata.Domain = metadata.Destination.Fqdn
|
||||
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||
r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose)
|
||||
return
|
||||
}
|
||||
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -113,6 +114,7 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
Timeout: C.TCPTimeout,
|
||||
}
|
||||
defer client.CloseIdleConnections()
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
|
||||
@@ -22,3 +22,21 @@ const (
|
||||
RuleSetVersion1 = 1 + iota
|
||||
RuleSetVersion2
|
||||
)
|
||||
|
||||
const (
|
||||
RuleActionTypeRoute = "route"
|
||||
RuleActionTypeReturn = "return"
|
||||
RuleActionTypeReject = "reject"
|
||||
RuleActionTypeHijackDNS = "hijack-dns"
|
||||
RuleActionTypeSniff = "sniff"
|
||||
RuleActionTypeResolve = "resolve"
|
||||
)
|
||||
|
||||
const (
|
||||
RuleActionRejectMethodDefault = "default"
|
||||
RuleActionRejectMethodReset = "reset"
|
||||
RuleActionRejectMethodNetworkUnreachable = "network-unreachable"
|
||||
RuleActionRejectMethodHostUnreachable = "host-unreachable"
|
||||
RuleActionRejectMethodPortUnreachable = "port-unreachable"
|
||||
RuleActionRejectMethodDrop = "drop"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,8 @@ import "time"
|
||||
const (
|
||||
TCPKeepAliveInitial = 10 * time.Minute
|
||||
TCPKeepAliveInterval = 75 * time.Second
|
||||
TCPTimeout = 5 * time.Second
|
||||
TCPConnectTimeout = 5 * time.Second
|
||||
TCPTimeout = 15 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
|
||||
@@ -2,11 +2,174 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.11.0-alpha.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.2
|
||||
|
||||
* Add warnings for usage of deprecated features
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.1
|
||||
|
||||
* Update quic-go to v0.48.0
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.10.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.10.0
|
||||
|
||||
Important changes since 1.9:
|
||||
|
||||
* Introducing auto-redirect **1**
|
||||
* Add AdGuard DNS Filter support **2**
|
||||
* TUN address fields are merged **3**
|
||||
* Add custom options for `auto-route` and `auto-redirect` **4**
|
||||
* Drop support for go1.18 and go1.19 **5**
|
||||
* Add tailing comma support in JSON configuration
|
||||
* Improve sniffers **6**
|
||||
* Add new `inline` rule-set type **7**
|
||||
* Add access control options for Clash API **8**
|
||||
* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9**
|
||||
* Add auto reload support for local rule-set
|
||||
* Update fsnotify usages **10**
|
||||
* Add IP address support for `rule-set match` command
|
||||
* Add `rule-set decompile` command
|
||||
* Add `process_path_regex` rule item
|
||||
* Update uTLS to v1.6.7 **11**
|
||||
* Optimize memory usages of rule-sets **12**
|
||||
|
||||
**1**:
|
||||
|
||||
The new auto-redirect feature allows TUN to automatically
|
||||
configure connection redirection to improve proxy performance.
|
||||
|
||||
When auto-redirect is enabled, new route address set options will allow you to
|
||||
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
||||
|
||||
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||
(for example, keep hardware offloading of direct traffics on the router).
|
||||
|
||||
See [TUN](/configuration/inbound/tun).
|
||||
|
||||
**2**:
|
||||
|
||||
The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home.
|
||||
|
||||
See [AdGuard DNS Filter](/configuration/rule-set/adguard/).
|
||||
|
||||
**3**:
|
||||
|
||||
See [Migration](/migration/#tun-address-fields-are-merged).
|
||||
|
||||
**4**:
|
||||
|
||||
See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),
|
||||
[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),
|
||||
[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and
|
||||
[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).
|
||||
|
||||
**5**:
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||
|
||||
**6**:
|
||||
|
||||
BitTorrent, DTLS, RDP, SSH sniffers are added.
|
||||
|
||||
Now the QUIC sniffer can correctly extract the server name from Chromium requests and
|
||||
can identify common QUIC clients, including
|
||||
Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome).
|
||||
|
||||
**7**:
|
||||
|
||||
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
||||
allows you to write headless rules directly without creating a rule-set file.
|
||||
|
||||
**8**:
|
||||
|
||||
With the new access control options, not only can you allow Clash dashboards
|
||||
to access the Clash API on your local network,
|
||||
you can also manually limit the websites that can access the API instead of allowing everyone.
|
||||
|
||||
See [Clash API](/configuration/experimental/clash-api/).
|
||||
|
||||
**9**:
|
||||
|
||||
See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).
|
||||
|
||||
**10**:
|
||||
|
||||
sing-box now uses fsnotify correctly and will not cancel watching
|
||||
if the target file is deleted or recreated via rename (e.g. `mv`).
|
||||
|
||||
This affects all path options that support reload, including
|
||||
`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.
|
||||
|
||||
**11**:
|
||||
|
||||
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
||||
see [utls](/configuration/shared/tls#utls).
|
||||
|
||||
**12**:
|
||||
|
||||
See [Source Format](/configuration/rule-set/source-format/#version).
|
||||
|
||||
### 1.9.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-beta.11
|
||||
|
||||
* Update uTLS to v1.6.7 **1**
|
||||
|
||||
**1**:
|
||||
|
||||
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
||||
see [utls](/configuration/shared/tls#utls).
|
||||
|
||||
#### 1.10.0-beta.10
|
||||
|
||||
* Add `process_path_regex` rule item
|
||||
* Fixes and improvements
|
||||
|
||||
_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of
|
||||
the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We
|
||||
will prompt users about this in feature versions._
|
||||
|
||||
### 1.9.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.9.5
|
||||
|
||||
* Update quic-go to v0.47.0
|
||||
* Fix direct dialer not resolving domain
|
||||
* Fix no error return when empty DNS cache retrieved
|
||||
* Fix build with go1.23
|
||||
* Fix stream sniffer
|
||||
* Fix bad redirect in clash-api
|
||||
* Fix wireguard events chan leak
|
||||
* Fix cached conn eats up read deadlines
|
||||
* Fix disconnected interface selected as default in windows
|
||||
* Update Bundle Identifiers for Apple platform clients **1**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients).
|
||||
|
||||
We are still working on getting all sing-box apps back on the App Store, which should be completed within a week
|
||||
(SFI on the App Store and others on TestFlight are already available).
|
||||
|
||||
#### 1.10.0-beta.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be completed within a month (TestFlight is already available)._
|
||||
_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be
|
||||
completed within a month (TestFlight is already available)._
|
||||
|
||||
#### 1.10.0-beta.7
|
||||
|
||||
@@ -48,6 +211,11 @@ _With the help of a netizen, we are in the process of getting sing-box apps back
|
||||
* Fix UDP connnection leak when sniffing
|
||||
* Fixes and improvements
|
||||
|
||||
_Due to problems with our Apple developer account,
|
||||
sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
||||
If your company or organization is willing to help us return to the App Store,
|
||||
please [contact us](mailto:contact@sagernet.org)._
|
||||
|
||||
#### 1.10.0-alpha.29
|
||||
|
||||
* Update quic-go to v0.46.0
|
||||
@@ -106,11 +274,9 @@ See [Source Format](/configuration/rule-set/source-format/#version).
|
||||
|
||||
**1**:
|
||||
|
||||
The new [rule-set] type inline (which also becomes the default type)
|
||||
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
||||
allows you to write headless rules directly without creating a rule-set file.
|
||||
|
||||
[rule-set]: /configuration/rule-set/
|
||||
|
||||
**2**:
|
||||
|
||||
sing-box now uses fsnotify correctly and will not cancel watching
|
||||
|
||||
@@ -40,6 +40,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
|
||||
|-----------------------|------------------|-----------------------------------|
|
||||
| `process_name` | :material-close: | No permission |
|
||||
| `process_path` | :material-close: | No permission |
|
||||
| `process_path_regex` | :material-close: | No permission |
|
||||
| `package_name` | :material-check: | / |
|
||||
| `user` | :material-close: | Use `package_name` instead |
|
||||
| `user_id` | :material-close: | Use `package_name` instead |
|
||||
|
||||
@@ -42,6 +42,7 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension
|
||||
|-----------------------|------------------|-----------------------|
|
||||
| `process_name` | :material-close: | No permission |
|
||||
| `process_path` | :material-close: | No permission |
|
||||
| `process_path_regex` | :material-close: | No permission |
|
||||
| `package_name` | :material-close: | / |
|
||||
| `user` | :material-close: | No permission |
|
||||
| `user_id` | :material-close: | No permission |
|
||||
|
||||
@@ -7,12 +7,6 @@ icon: material/apple
|
||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||
|
||||
!!! failure "Unavailable"
|
||||
|
||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
||||
|
||||
We are working on getting sing-box apps back on the App Store, which should be completed within a month (TestFlight is already available).
|
||||
|
||||
## :material-graph: Requirements
|
||||
|
||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||
@@ -20,7 +14,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
||||
|
||||
## :material-download: Download
|
||||
|
||||
* ~~[App Store](https://apps.apple.com/us/app/sing-box/id6451272673)~~
|
||||
* [App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)
|
||||
* TestFlight (Beta)
|
||||
|
||||
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
||||
|
||||
@@ -6,7 +6,8 @@ icon: material/new-box
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
:material-plus: [process_path_regex](#process_path_regex)
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
@@ -103,6 +104,9 @@ icon: material/new-box
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"process_path_regex": [
|
||||
"^/usr/bin/.+"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
@@ -268,6 +272,16 @@ Match process name.
|
||||
|
||||
Match process path.
|
||||
|
||||
#### process_path_regex
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, Windows, and macOS.
|
||||
|
||||
Match process path using regular expression.
|
||||
|
||||
#### package_name
|
||||
|
||||
Match android package name.
|
||||
|
||||
@@ -6,7 +6,8 @@ icon: material/new-box
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
:material-plus: [process_path_regex](#process_path_regex)
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
@@ -103,6 +104,9 @@ icon: material/new-box
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"process_path_regex": [
|
||||
"^/usr/bin/.+"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
@@ -266,6 +270,16 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
匹配进程路径。
|
||||
|
||||
#### process_path_regex
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS.
|
||||
|
||||
使用正则表达式匹配进程路径。
|
||||
|
||||
#### package_name
|
||||
|
||||
匹配 Android 应用包名。
|
||||
|
||||
@@ -47,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### fallback
|
||||
|
||||
!!! quote ""
|
||||
!!! failure ""
|
||||
|
||||
There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.
|
||||
|
||||
|
||||
@@ -232,12 +232,12 @@ Automatically configure iptables/nftables to redirect connections.
|
||||
|
||||
*In Android*:
|
||||
|
||||
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||
|
||||
*In Linux*:
|
||||
|
||||
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
||||
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
||||
|
||||
#### auto_redirect_input_mark
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||
|
||||
自动配置 iptables 以重定向 TCP 连接。
|
||||
自动配置 iptables/nftables 以重定向连接。
|
||||
|
||||
*在 Android 中*:
|
||||
|
||||
@@ -240,7 +240,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
||||
带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。
|
||||
|
||||
#### auto_redirect_input_mark
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [client](#client)
|
||||
:material-plus: [client](#client)
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
:material-plus: [process_path_regex](#process_path_regex)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
@@ -101,6 +102,9 @@ icon: material/alert-decagram
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"process_path_regex": [
|
||||
"^/usr/bin/.+"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
@@ -277,6 +281,16 @@ Match process name.
|
||||
|
||||
Match process path.
|
||||
|
||||
#### process_path_regex
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, Windows, and macOS.
|
||||
|
||||
Match process path using regular expression.
|
||||
|
||||
#### package_name
|
||||
|
||||
Match android package name.
|
||||
|
||||
@@ -6,7 +6,8 @@ icon: material/alert-decagram
|
||||
|
||||
:material-plus: [client](#client)
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
:material-plus: [process_path_regex](#process_path_regex)
|
||||
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
@@ -99,6 +100,9 @@ icon: material/alert-decagram
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"process_path_regex": [
|
||||
"^/usr/bin/.+"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
@@ -275,6 +279,16 @@ icon: material/alert-decagram
|
||||
|
||||
匹配进程路径。
|
||||
|
||||
#### process_path_regex
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS.
|
||||
|
||||
使用正则表达式匹配进程路径。
|
||||
|
||||
#### package_name
|
||||
|
||||
匹配 Android 应用包名。
|
||||
|
||||
@@ -57,6 +57,9 @@
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"process_path_regex": [
|
||||
"^/usr/bin/.+"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
@@ -160,6 +163,16 @@ Match process name.
|
||||
|
||||
Match process path.
|
||||
|
||||
#### process_path_regex
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, Windows, and macOS.
|
||||
|
||||
Match process path using regular expression.
|
||||
|
||||
#### package_name
|
||||
|
||||
Match android package name.
|
||||
|
||||
@@ -83,7 +83,10 @@
|
||||
|
||||
如果设置,域名将在请求发出之前解析为 IP。
|
||||
|
||||
默认使用 `dns.strategy`。
|
||||
| 出站 | 受影响的域名 | 默认回退值 |
|
||||
|----------|--------------------------|-------------------------------------------|
|
||||
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||
| others | 服务器地址中的域名 | / |
|
||||
|
||||
#### fallback_delay
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-alert-decagram: [utls](#utls)
|
||||
|
||||
@@ -210,28 +214,25 @@ The path to the server private key, in PEM format.
|
||||
|
||||
==Client only==
|
||||
|
||||
!!! note ""
|
||||
|
||||
uTLS is poorly maintained and the effect may be unproven, use at your own risk.
|
||||
!!! failure ""
|
||||
|
||||
There is no evidence that GFW detects and blocks servers based on TLS client fingerprinting, and using an imperfect emulation that has not been security reviewed could pose security risks.
|
||||
|
||||
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.
|
||||
|
||||
Available fingerprint values:
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
!!! warning "Removed since sing-box 1.10.0"
|
||||
|
||||
:material-plus: chrome_psk
|
||||
:material-plus: chrome_psk_shuffle
|
||||
:material-plus: chrome_padding_psk_shuffle
|
||||
:material-plus: chrome_pq
|
||||
:material-plus: chrome_pq_psk
|
||||
Some legacy chrome fingerprints have been removed and will fallback to chrome:
|
||||
|
||||
:material-close: chrome_psk
|
||||
:material-close: chrome_psk_shuffle
|
||||
:material-close: chrome_padding_psk_shuffle
|
||||
:material-close: chrome_pq
|
||||
:material-close: chrome_pq_psk
|
||||
|
||||
* chrome
|
||||
* chrome_psk
|
||||
* chrome_psk_shuffle
|
||||
* chrome_padding_psk_shuffle
|
||||
* chrome_pq
|
||||
* chrome_pq_psk
|
||||
* firefox
|
||||
* edge
|
||||
* safari
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-alert-decagram: [utls](#utls)
|
||||
|
||||
@@ -44,8 +48,8 @@
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 拨号字段
|
||||
...
|
||||
// 拨号字段
|
||||
},
|
||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||
"short_id": [
|
||||
@@ -202,28 +206,25 @@ TLS 版本值:
|
||||
|
||||
==仅客户端==
|
||||
|
||||
!!! note ""
|
||||
!!! failure ""
|
||||
|
||||
uTLS 维护不善且其效果可能未经证实,使用风险自负。
|
||||
没有证据表明 GFW 根据 TLS 客户端指纹检测并阻止服务器,并且,使用一个未经安全审查的不完美模拟可能带来安全隐患。
|
||||
|
||||
uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。
|
||||
|
||||
可用的指纹值:
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
!!! warning "已在 sing-box 1.10.0 移除"
|
||||
|
||||
:material-plus: chrome_psk
|
||||
:material-plus: chrome_psk_shuffle
|
||||
:material-plus: chrome_padding_psk_shuffle
|
||||
:material-plus: chrome_pq
|
||||
:material-plus: chrome_pq_psk
|
||||
一些旧 chrome 指纹已被删除,并将会退到 chrome:
|
||||
|
||||
:material-close: chrome_psk
|
||||
:material-close: chrome_psk_shuffle
|
||||
:material-close: chrome_padding_psk_shuffle
|
||||
:material-close: chrome_pq
|
||||
:material-close: chrome_pq_psk
|
||||
|
||||
* chrome
|
||||
* chrome_psk
|
||||
* chrome_psk_shuffle
|
||||
* chrome_padding_psk_shuffle
|
||||
* chrome_pq
|
||||
* chrome_pq_psk
|
||||
* firefox
|
||||
* edge
|
||||
* safari
|
||||
|
||||
@@ -14,6 +14,11 @@ icon: material/delete-alert
|
||||
|
||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||
|
||||
#### Match source rule items are renamed
|
||||
|
||||
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
||||
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||
|
||||
#### Drop support for go1.18 and go1.19
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||
|
||||
@@ -6,13 +6,18 @@ icon: material/delete-alert
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### Match source 规则项已重命名
|
||||
|
||||
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
|
||||
`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||
|
||||
#### TUN 地址字段已合并
|
||||
|
||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中被移除。
|
||||
|
||||
#### 移除对 go1.18 和 go1.19 的支持
|
||||
|
||||
|
||||
@@ -6,26 +6,18 @@ icon: material/file-code
|
||||
|
||||
## :material-graph: Requirements
|
||||
|
||||
Before sing-box 1.4.0:
|
||||
### sing-box 1.10
|
||||
|
||||
* Go 1.18.5 - 1.20.x
|
||||
|
||||
Since sing-box 1.4.0:
|
||||
|
||||
* Go 1.18.5 - ~
|
||||
* Go 1.20.0 - ~ with tag `with_quic` enabled
|
||||
|
||||
Since sing-box 1.5.0:
|
||||
|
||||
* Go 1.18.5 - ~
|
||||
* Go 1.20.0 - ~ with tag `with_quic` or `with_ech` enabled
|
||||
|
||||
Since sing-box 1.8.0:
|
||||
|
||||
* Go 1.18.5 - ~
|
||||
* Go 1.20.0 - ~
|
||||
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
||||
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
||||
|
||||
### sing-box 1.9
|
||||
|
||||
* Go 1.18.5 - 1.22.x
|
||||
* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled
|
||||
* Go 1.21.0 - 1.22.x with tag `with_ech` enabled
|
||||
|
||||
You can download and install Go from: https://go.dev/doc/install, latest version is recommended.
|
||||
|
||||
## :material-fast-forward: Simple Build
|
||||
|
||||
@@ -6,25 +6,17 @@ icon: material/file-code
|
||||
|
||||
## :material-graph: 要求
|
||||
|
||||
sing-box 1.4.0 前:
|
||||
### sing-box 1.10
|
||||
|
||||
* Go 1.18.5 - 1.20.x
|
||||
* Go 1.20.0 - ~
|
||||
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
||||
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
||||
|
||||
从 sing-box 1.4.0:
|
||||
### sing-box 1.9
|
||||
|
||||
* Go 1.18.5 - ~
|
||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic`
|
||||
|
||||
从 sing-box 1.5.0:
|
||||
|
||||
* Go 1.18.5 - ~
|
||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_ech`
|
||||
|
||||
从 sing-box 1.8.0:
|
||||
|
||||
* Go 1.18.5 - ~
|
||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_utls`
|
||||
* Go 1.20.1 - ~ 如果启用构建标记 `with_ech`
|
||||
* Go 1.18.5 - 1.22.x
|
||||
* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled
|
||||
* Go 1.21.0 - 1.22.x with tag `with_ech` enabled
|
||||
|
||||
您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。
|
||||
|
||||
|
||||
@@ -87,12 +87,12 @@ icon: material/package
|
||||
|
||||
## :material-alert: Problematic Sources
|
||||
|
||||
| Type | Platform | Link | Promblem(s) |
|
||||
|------------|----------|-------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification; Not actively maintained |
|
||||
| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification |
|
||||
| Termux | Android | [termux-packages][termux] | Problematic build tag list modification |
|
||||
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) |
|
||||
| Type | Platform | Link | Promblem(s) |
|
||||
|------------|----------|-------------------------------------------------------------------------------------------|-----------------------------------------|
|
||||
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification |
|
||||
| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification |
|
||||
| Termux | Android | [termux-packages][termux] | Problematic build tag list modification |
|
||||
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) |
|
||||
|
||||
If you are a user of them, please report issues to them:
|
||||
|
||||
|
||||
@@ -85,15 +85,14 @@ icon: material/package
|
||||
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||
|
||||
|
||||
## :material-alert: 存在问题的源
|
||||
|
||||
| 类型 | 平台 | 链接 | 原因 |
|
||||
|------------|---------|-------------------------------------------------------------------------------------------|-----------------------|
|
||||
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改; 没有活跃维护 |
|
||||
| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 |
|
||||
| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 |
|
||||
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) |
|
||||
| 类型 | 平台 | 链接 | 原因 |
|
||||
|------------|---------|-------------------------------------------------------------------------------------------|-----------------|
|
||||
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改 |
|
||||
| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 |
|
||||
| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 |
|
||||
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) |
|
||||
|
||||
如果您是其用户,请向他们报告问题:
|
||||
|
||||
|
||||
@@ -70,6 +70,23 @@ Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||
}
|
||||
```
|
||||
|
||||
## 1.9.5
|
||||
|
||||
### Bundle Identifier updates in Apple platform clients
|
||||
|
||||
Due to problems with our old Apple developer account,
|
||||
we can only change Bundle Identifiers to re-list sing-box apps,
|
||||
which means the data will not be automatically inherited.
|
||||
|
||||
For iOS, you need to back up your old data yourself (if you still have access to it);
|
||||
for tvOS, you need to re-import profiles from your iPhone or iPad or create it manually;
|
||||
for macOS, you can migrate the data folder using the following command:
|
||||
|
||||
```bash
|
||||
cd ~/Library/Group\ Containers && \
|
||||
mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt
|
||||
```
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### `domain_suffix` behavior update
|
||||
|
||||
@@ -70,6 +70,22 @@ icon: material/arrange-bring-forward
|
||||
}
|
||||
```
|
||||
|
||||
## 1.9.5
|
||||
|
||||
### Apple 平台客户端的 Bundle Identifier 更新
|
||||
|
||||
由于我们旧的苹果开发者账户存在问题,我们只能通过更新 Bundle Identifiers
|
||||
来重新上架 sing-box 应用, 这意味着数据不会自动继承。
|
||||
|
||||
对于 iOS,您需要自行备份旧的数据(如果您仍然可以访问);
|
||||
对于 Apple tvOS,您需要从 iPhone 或 iPad 重新导入配置或者手动创建;
|
||||
对于 macOS,您可以使用以下命令迁移数据文件夹:
|
||||
|
||||
```bash
|
||||
cd ~/Library/Group\ Containers && \
|
||||
mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt
|
||||
```
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### `domain_suffix` 行为更新
|
||||
|
||||
26
docs/sponsors.md
Normal file
26
docs/sponsors.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
icon: material/hand-coin
|
||||
---
|
||||
|
||||
# Sponsors
|
||||
|
||||
Do you or your friends use sing-box?
|
||||
|
||||
You can help keep the project bug-free and feature rich by sponsoring
|
||||
the project maintainer via [GitHub Sponsors](https://github.com/sponsors/nekohasekai).
|
||||
|
||||

|
||||
|
||||
### Special Sponsors
|
||||
|
||||
**Viral Tech, Inc.**
|
||||
|
||||
Helping us re-list sing-box apps on the Apple Store.
|
||||
|
||||
---
|
||||
|
||||
[](https://www.jetbrains.com)
|
||||
|
||||
Free license for the amazing IDEs.
|
||||
|
||||
---
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
@@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||
group, ok := proxy.(adapter.OutboundGroup)
|
||||
outboundGroup, ok := proxy.(adapter.OutboundGroup)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
@@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]uint16
|
||||
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
|
||||
if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup {
|
||||
result, err = urlTestGroup.URLTest(ctx)
|
||||
} else {
|
||||
outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
|
||||
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||
itOutbound, _ := server.router.Outbound(it)
|
||||
return itOutbound
|
||||
}))
|
||||
@@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||
var resultAccess sync.Mutex
|
||||
for _, detour := range outbounds {
|
||||
tag := detour.Tag()
|
||||
realTag := outbound.RealTag(detour)
|
||||
realTag := group.RealTag(detour)
|
||||
if checked[realTag] {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
@@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||
selector, ok := proxy.(*outbound.Selector)
|
||||
selector, ok := proxy.(*group.Selector)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("Must be a Selector"))
|
||||
@@ -204,7 +204,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
delay, err := urltest.URLTest(ctx, url, proxy)
|
||||
defer func() {
|
||||
realTag := outbound.RealTag(proxy)
|
||||
realTag := group.RealTag(proxy)
|
||||
if err != nil {
|
||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||
} else {
|
||||
|
||||
@@ -30,10 +30,9 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request
|
||||
rules = append(rules, Rule{
|
||||
Type: rule.Type(),
|
||||
Payload: rule.String(),
|
||||
Proxy: rule.Outbound(),
|
||||
Proxy: rule.Action().String(),
|
||||
})
|
||||
}
|
||||
|
||||
render.JSON(w, r, render.M{
|
||||
"rules": rules,
|
||||
})
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -60,7 +60,7 @@ func (s *Server) downloadExternalUI() error {
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
TLSHandshakeTimeout: C.TCPTimeout,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@@ -60,7 +61,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
var rule string
|
||||
if t.Rule != nil {
|
||||
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
||||
rule = F.ToString(t.Rule, " => ", t.Rule.Action())
|
||||
} else {
|
||||
rule = "final"
|
||||
}
|
||||
@@ -131,19 +132,21 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
|
||||
outbound string
|
||||
outboundType string
|
||||
)
|
||||
if rule == nil {
|
||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
}
|
||||
} else {
|
||||
next = rule.Outbound()
|
||||
var action adapter.RuleAction
|
||||
if rule != nil {
|
||||
action = rule.Action()
|
||||
}
|
||||
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
||||
next = routeAction.Outbound
|
||||
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
}
|
||||
for {
|
||||
chain = append(chain, next)
|
||||
detour, loaded := router.Outbound(next)
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
chain = append(chain, next)
|
||||
outbound = detour.Tag()
|
||||
outboundType = detour.Type()
|
||||
group, isGroup := detour.(adapter.OutboundGroup)
|
||||
@@ -218,19 +221,21 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
|
||||
outbound string
|
||||
outboundType string
|
||||
)
|
||||
if rule == nil {
|
||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
}
|
||||
} else {
|
||||
next = rule.Outbound()
|
||||
var action adapter.RuleAction
|
||||
if rule != nil {
|
||||
action = rule.Action()
|
||||
}
|
||||
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
||||
next = routeAction.Outbound
|
||||
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
}
|
||||
for {
|
||||
chain = append(chain, next)
|
||||
detour, loaded := router.Outbound(next)
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
chain = append(chain, next)
|
||||
outbound = detour.Tag()
|
||||
outboundType = detour.Type()
|
||||
group, isGroup := detour.(adapter.OutboundGroup)
|
||||
|
||||
86
experimental/deprecated/constants.go
Normal file
86
experimental/deprecated/constants.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
type Note struct {
|
||||
Name string
|
||||
Description string
|
||||
DeprecatedVersion string
|
||||
ScheduledVersion string
|
||||
EnvName string
|
||||
MigrationLink string
|
||||
}
|
||||
|
||||
func (n Note) Impending() bool {
|
||||
if n.ScheduledVersion == "" {
|
||||
return false
|
||||
}
|
||||
if !semver.IsValid("v" + C.Version) {
|
||||
return false
|
||||
}
|
||||
versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion)
|
||||
if versionMinor < 0 {
|
||||
panic("invalid deprecated note: " + n.Name)
|
||||
}
|
||||
return versionMinor <= 1
|
||||
}
|
||||
|
||||
func (n Note) Message() string {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
|
||||
)
|
||||
}
|
||||
|
||||
func (n Note) MessageWithLink() string {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink,
|
||||
)
|
||||
}
|
||||
|
||||
var OptionBadMatchSource = Note{
|
||||
Name: "bad-match-source",
|
||||
Description: "legacy match source rule item",
|
||||
DeprecatedVersion: "1.10.0",
|
||||
ScheduledVersion: "1.11.0",
|
||||
MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
|
||||
}
|
||||
|
||||
var OptionGEOIP = Note{
|
||||
Name: "geoip",
|
||||
Description: "geoip database",
|
||||
DeprecatedVersion: "1.8.0",
|
||||
ScheduledVersion: "1.12.0",
|
||||
EnvName: "GEOIP",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geoip-to-rule-sets",
|
||||
}
|
||||
|
||||
var OptionGEOSITE = Note{
|
||||
Name: "geosite",
|
||||
Description: "geosite database",
|
||||
DeprecatedVersion: "1.8.0",
|
||||
ScheduledVersion: "1.12.0",
|
||||
EnvName: "GEOSITE",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets",
|
||||
}
|
||||
|
||||
var OptionTUNAddressX = Note{
|
||||
Name: "tun-address-x",
|
||||
Description: "legacy tun address fields",
|
||||
DeprecatedVersion: "1.10.0",
|
||||
ScheduledVersion: "1.12.0",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
|
||||
}
|
||||
|
||||
var Options = []Note{
|
||||
OptionBadMatchSource,
|
||||
OptionGEOIP,
|
||||
OptionGEOSITE,
|
||||
OptionTUNAddressX,
|
||||
}
|
||||
30
experimental/deprecated/env.go
Normal file
30
experimental/deprecated/env.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type envManager struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewEnvManager(logger logger.Logger) Manager {
|
||||
return &envManager{logger: logger}
|
||||
}
|
||||
|
||||
func (f *envManager) ReportDeprecated(feature Note) {
|
||||
if !feature.Impending() {
|
||||
f.logger.Warn(feature.MessageWithLink())
|
||||
return
|
||||
}
|
||||
enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName))
|
||||
if enableErr == nil && enable {
|
||||
f.logger.Warn(feature.MessageWithLink())
|
||||
return
|
||||
}
|
||||
f.logger.Error(feature.MessageWithLink())
|
||||
f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true")
|
||||
}
|
||||
19
experimental/deprecated/manager.go
Normal file
19
experimental/deprecated/manager.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
type Manager interface {
|
||||
ReportDeprecated(feature Note)
|
||||
}
|
||||
|
||||
func Report(ctx context.Context, feature Note) {
|
||||
manager := service.FromContext[Manager](ctx)
|
||||
if manager == nil {
|
||||
return
|
||||
}
|
||||
manager.ReportDeprecated(feature)
|
||||
}
|
||||
@@ -16,4 +16,5 @@ const (
|
||||
CommandSetSystemProxyEnabled
|
||||
CommandConnections
|
||||
CommandCloseConnection
|
||||
CommandGetDeprecatedNotes
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ type CommandClient struct {
|
||||
type CommandClientOptions struct {
|
||||
Command int32
|
||||
StatusInterval int64
|
||||
IsMainClient bool
|
||||
}
|
||||
|
||||
type CommandClientHandler interface {
|
||||
@@ -28,6 +29,7 @@ type CommandClientHandler interface {
|
||||
ClearLogs()
|
||||
WriteLogs(messageList StringIterator)
|
||||
WriteStatus(message *StatusMessage)
|
||||
OpenURL(url string)
|
||||
WriteGroups(message OutboundGroupIterator)
|
||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||
UpdateClashMode(newMode string)
|
||||
@@ -91,9 +93,13 @@ func (c *CommandClient) Connect() error {
|
||||
c.handler.Connected()
|
||||
go c.handleLogConn(conn)
|
||||
case CommandStatus:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write is main client")
|
||||
}
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
return E.Cause(err, "write header")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleStatusConn(conn)
|
||||
|
||||
@@ -18,6 +18,10 @@ func (c *CommandClient) CloseConnection(connId string) error {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer := bufio.NewWriter(conn)
|
||||
err = varbin.Write(writer, binary.BigEndian, connId)
|
||||
if err != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
|
||||
connections Connections
|
||||
)
|
||||
for {
|
||||
rawConnections = nil
|
||||
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
|
||||
46
experimental/libbox/command_deprecated_report.go
Normal file
46
experimental/libbox/command_deprecated_report.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = readError(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var features []deprecated.Note
|
||||
err = varbin.Read(conn, binary.BigEndian, &features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error {
|
||||
boxService := s.service
|
||||
if boxService == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
err := writeError(conn, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get())
|
||||
}
|
||||
40
experimental/libbox/command_event.go
Normal file
40
experimental/libbox/command_event.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
type myEvent interface {
|
||||
writeTo(writer varbin.Writer)
|
||||
}
|
||||
|
||||
func readEvent(reader varbin.Reader) (myEvent, error) {
|
||||
eventType, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch eventType {
|
||||
case eventTypeEmpty:
|
||||
return nil, nil
|
||||
case eventTypeOpenURL:
|
||||
url, err := varbin.ReadValue[string](reader, binary.BigEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &eventOpenURL{URL: url}, nil
|
||||
default:
|
||||
return nil, E.New("unknown event type: ", eventType)
|
||||
}
|
||||
}
|
||||
|
||||
type eventOpenURL struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
func (e *eventOpenURL) writeTo(writer varbin.Writer) {
|
||||
writer.WriteByte(eventTypeOpenURL)
|
||||
varbin.Write(writer, binary.BigEndian, e.URL)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
||||
}
|
||||
var groups []OutboundGroup
|
||||
for _, iGroup := range iGroups {
|
||||
var group OutboundGroup
|
||||
group.Tag = iGroup.Tag()
|
||||
group.Type = iGroup.Type()
|
||||
_, group.Selectable = iGroup.(*outbound.Selector)
|
||||
group.Selected = iGroup.Now()
|
||||
var outboundGroup OutboundGroup
|
||||
outboundGroup.Tag = iGroup.Tag()
|
||||
outboundGroup.Type = iGroup.Type()
|
||||
_, outboundGroup.Selectable = iGroup.(*group.Selector)
|
||||
outboundGroup.Selected = iGroup.Now()
|
||||
if cacheFile != nil {
|
||||
if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
|
||||
group.IsExpand = isExpand
|
||||
if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded {
|
||||
outboundGroup.IsExpand = isExpand
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
||||
item.URLTestTime = history.Time.Unix()
|
||||
item.URLTestDelay = int32(history.Delay)
|
||||
}
|
||||
group.ItemList = append(group.ItemList, &item)
|
||||
outboundGroup.ItemList = append(outboundGroup.ItemList, &item)
|
||||
}
|
||||
if len(group.ItemList) < 2 {
|
||||
if len(outboundGroup.ItemList) < 2 {
|
||||
continue
|
||||
}
|
||||
groups = append(groups, group)
|
||||
groups = append(groups, outboundGroup)
|
||||
}
|
||||
return varbin.Write(writer, binary.BigEndian, groups)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
@@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
|
||||
if !isLoaded {
|
||||
return writeError(conn, E.New("selector not found: ", groupTag))
|
||||
}
|
||||
selector, isSelector := outboundGroup.(*outbound.Selector)
|
||||
selector, isSelector := outboundGroup.(*group.Selector)
|
||||
if !isSelector {
|
||||
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type CommandServer struct {
|
||||
urlTestUpdate chan struct{}
|
||||
modeUpdate chan struct{}
|
||||
logReset chan struct{}
|
||||
events chan myEvent
|
||||
|
||||
closedConnections []Connection
|
||||
}
|
||||
@@ -52,6 +53,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
|
||||
urlTestUpdate: make(chan struct{}, 1),
|
||||
modeUpdate: make(chan struct{}, 1),
|
||||
logReset: make(chan struct{}, 1),
|
||||
events: make(chan myEvent, 8),
|
||||
}
|
||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||
return server
|
||||
@@ -61,6 +63,12 @@ func (s *CommandServer) SetService(newService *BoxService) {
|
||||
if newService != nil {
|
||||
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
||||
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
||||
newService.platformInterface.openURLFunc = func(url string) {
|
||||
select {
|
||||
case s.events <- &eventOpenURL{URL: url}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
s.service = newService
|
||||
s.notifyURLTestUpdate()
|
||||
@@ -174,6 +182,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||
return s.handleConnectionsConn(conn)
|
||||
case CommandCloseConnection:
|
||||
return s.handleCloseConnection(conn)
|
||||
case CommandGetDeprecatedNotes:
|
||||
return s.handleGetDeprecatedNotes(conn)
|
||||
default:
|
||||
return E.New("unknown command: ", command)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
std_bufio "bufio"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"runtime"
|
||||
@@ -9,9 +10,15 @@ import (
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
const (
|
||||
eventTypeEmpty byte = iota
|
||||
eventTypeOpenURL
|
||||
)
|
||||
|
||||
type StatusMessage struct {
|
||||
Memory int64
|
||||
Goroutines int32
|
||||
@@ -44,31 +51,73 @@ func (s *CommandServer) readStatus() StatusMessage {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
||||
var isMainClient bool
|
||||
err := binary.Read(conn, binary.BigEndian, &isMainClient)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read is main client")
|
||||
}
|
||||
var interval int64
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
err = binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
ctx := connKeepAlive(conn)
|
||||
for {
|
||||
err = binary.Write(conn, binary.BigEndian, s.readStatus())
|
||||
if err != nil {
|
||||
return err
|
||||
writer := std_bufio.NewWriter(conn)
|
||||
if isMainClient {
|
||||
for {
|
||||
writer.WriteByte(eventTypeEmpty)
|
||||
err = binary.Write(conn, binary.BigEndian, s.readStatus())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.Flush()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
case event := <-s.events:
|
||||
event.writeTo(writer)
|
||||
writer.Flush()
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
} else {
|
||||
for {
|
||||
err = binary.Write(conn, binary.BigEndian, s.readStatus())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.Flush()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleStatusConn(conn net.Conn) {
|
||||
reader := std_bufio.NewReader(conn)
|
||||
for {
|
||||
if c.options.IsMainClient {
|
||||
rawEvent, err := readEvent(reader)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
switch event := rawEvent.(type) {
|
||||
case *eventOpenURL:
|
||||
c.handler.OpenURL(event.URL)
|
||||
continue
|
||||
case nil:
|
||||
default:
|
||||
panic(F.ToString("unexpected event type: ", event))
|
||||
}
|
||||
}
|
||||
var message StatusMessage
|
||||
err := binary.Read(conn, binary.BigEndian, &message)
|
||||
err := binary.Read(reader, binary.BigEndian, &message)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
if !isOutboundGroup {
|
||||
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
||||
}
|
||||
urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest)
|
||||
urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
|
||||
if isURLTest {
|
||||
go urlTest.CheckOutbounds()
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -16,10 +18,11 @@ import (
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func parseConfig(configContent string) (option.Options, error) {
|
||||
options, err := json.UnmarshalExtended[option.Options]([]byte(configContent))
|
||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "decode config")
|
||||
}
|
||||
@@ -27,16 +30,16 @@ func parseConfig(configContent string) (option.Options, error) {
|
||||
}
|
||||
|
||||
func CheckConfig(configContent string) error {
|
||||
options, err := parseConfig(configContent)
|
||||
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil))
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
PlatformInterface: (*platformInterfaceStub)(nil),
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
})
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
@@ -134,8 +137,11 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
|
||||
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) OpenURL(url string) {
|
||||
}
|
||||
|
||||
func FormatConfig(configContent string) (string, error) {
|
||||
options, err := parseConfig(configContent)
|
||||
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
56
experimental/libbox/deprecated.go
Normal file
56
experimental/libbox/deprecated.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
)
|
||||
|
||||
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
||||
|
||||
type deprecatedManager struct {
|
||||
access sync.Mutex
|
||||
features []deprecated.Note
|
||||
}
|
||||
|
||||
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.features = append(m.features, feature)
|
||||
}
|
||||
|
||||
func (m *deprecatedManager) Get() []deprecated.Note {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
features := m.features
|
||||
m.features = nil
|
||||
return features
|
||||
}
|
||||
|
||||
var _ = deprecated.Note(DeprecatedNote{})
|
||||
|
||||
type DeprecatedNote struct {
|
||||
Name string
|
||||
Description string
|
||||
DeprecatedVersion string
|
||||
ScheduledVersion string
|
||||
EnvName string
|
||||
MigrationLink string
|
||||
}
|
||||
|
||||
func (n DeprecatedNote) Impending() bool {
|
||||
return deprecated.Note(n).Impending()
|
||||
}
|
||||
|
||||
func (n DeprecatedNote) Message() string {
|
||||
return deprecated.Note(n).Message()
|
||||
}
|
||||
|
||||
func (n DeprecatedNote) MessageWithLink() string {
|
||||
return deprecated.Note(n).MessageWithLink()
|
||||
}
|
||||
|
||||
type DeprecatedNoteIterator interface {
|
||||
HasNext() bool
|
||||
Next() *DeprecatedNote
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user