mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-16 05:39:08 +10:00
Compare commits
111 Commits
v1.5.0-bet
...
v1.7.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47e2ea9f0c | ||
|
|
70fa176dbf | ||
|
|
a1278689f4 | ||
|
|
d339fcf558 | ||
|
|
4fa26c74d0 | ||
|
|
57fd57d3af | ||
|
|
9e2319c85d | ||
|
|
3cadc90375 | ||
|
|
2c6967d7f9 | ||
|
|
fe866b123a | ||
|
|
cbef1b1e59 | ||
|
|
e21f84932c | ||
|
|
7a679bc328 | ||
|
|
6635dd9abc | ||
|
|
ce164724ea | ||
|
|
a3ef7a7d88 | ||
|
|
71218ef0d3 | ||
|
|
e777b4c6dc | ||
|
|
6815f94180 | ||
|
|
b013acd89d | ||
|
|
f7c2eb6e76 | ||
|
|
3ef9b1b343 | ||
|
|
2224c68959 | ||
|
|
bb7d03d1db | ||
|
|
50036924e8 | ||
|
|
c2c3f7284f | ||
|
|
f6fee53676 | ||
|
|
63b8e8ed23 | ||
|
|
6ae86eda98 | ||
|
|
267d9617b7 | ||
|
|
0a06ccae50 | ||
|
|
8de0fad9f5 | ||
|
|
e05bf6308e | ||
|
|
a20a0cb455 | ||
|
|
d29f7475d2 | ||
|
|
aaa6702863 | ||
|
|
bb928f096a | ||
|
|
9f01d5c5b4 | ||
|
|
11629a931b | ||
|
|
126f825241 | ||
|
|
998cc7bd22 | ||
|
|
3efccaa8f5 | ||
|
|
d57b35ec30 | ||
|
|
e82dab027d | ||
|
|
9350f3983b | ||
|
|
53b123241f | ||
|
|
97286eea1e | ||
|
|
343e24969d | ||
|
|
31c294d998 | ||
|
|
3b161ab30c | ||
|
|
41fd1778a7 | ||
|
|
ac930cf1aa | ||
|
|
e143fc510d | ||
|
|
bea177a4cd | ||
|
|
aa05a4d050 | ||
|
|
a8112ff824 | ||
|
|
a7710c3845 | ||
|
|
cb2e15f8a7 | ||
|
|
23aa8a0543 | ||
|
|
edf7d046eb | ||
|
|
de0b5cc1c2 | ||
|
|
2686e8afea | ||
|
|
d9853ca2be | ||
|
|
b617eb5adf | ||
|
|
ddf38799e2 | ||
|
|
5291d43dc8 | ||
|
|
a634830d85 | ||
|
|
e5d191ca73 | ||
|
|
2371f0fd51 | ||
|
|
cfdce7a96f | ||
|
|
dc8ac01dec | ||
|
|
5f18738b2b | ||
|
|
7b4e4ca2d0 | ||
|
|
01ba4668b6 | ||
|
|
e782d21806 | ||
|
|
00155d61fc | ||
|
|
8f2273a2b4 | ||
|
|
0d0526afa2 | ||
|
|
ac2d07b61a | ||
|
|
d35487f422 | ||
|
|
2749f4a013 | ||
|
|
45c679648e | ||
|
|
5f2f7fc8b9 | ||
|
|
83c79102cf | ||
|
|
8b95292e53 | ||
|
|
3de7a2ddd3 | ||
|
|
8437a6cb4e | ||
|
|
9c4d08c6e1 | ||
|
|
e26096085e | ||
|
|
2f1b2199c5 | ||
|
|
af791db01f | ||
|
|
abcf030d89 | ||
|
|
7840dc73e3 | ||
|
|
df9050400e | ||
|
|
fdd38d6cf8 | ||
|
|
9891fd672f | ||
|
|
92a84ee112 | ||
|
|
992331f17e | ||
|
|
4fb227ed86 | ||
|
|
5a1ddea100 | ||
|
|
fbaa2f9de9 | ||
|
|
97ab9bb194 | ||
|
|
61ac141124 | ||
|
|
d4d49d9df5 | ||
|
|
c60a944aac | ||
|
|
17584c245f | ||
|
|
6e84b694a4 | ||
|
|
34a93171f0 | ||
|
|
678f6ef72f | ||
|
|
ae8187ed15 | ||
|
|
12dd1ac87f |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -46,6 +46,7 @@ body:
|
||||
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
||||
value: |-
|
||||
<details>
|
||||
|
||||
```console
|
||||
# Replace this line with the output
|
||||
```
|
||||
@@ -71,6 +72,7 @@ body:
|
||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
||||
value: |-
|
||||
<details>
|
||||
|
||||
```console
|
||||
# Replace this line with logs
|
||||
```
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -46,6 +46,7 @@ body:
|
||||
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
||||
value: |-
|
||||
<details>
|
||||
|
||||
```console
|
||||
# 使用输出内容覆盖此行
|
||||
```
|
||||
@@ -71,6 +72,7 @@ body:
|
||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
||||
value: |-
|
||||
<details>
|
||||
|
||||
```console
|
||||
# 使用日志内容覆盖此行
|
||||
```
|
||||
|
||||
10
.github/workflows/debug.yml
vendored
10
.github/workflows/debug.yml
vendored
@@ -3,6 +3,7 @@ name: Debug build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable-next
|
||||
- main-next
|
||||
- dev-next
|
||||
paths-ignore:
|
||||
@@ -11,6 +12,7 @@ on:
|
||||
- '!.github/workflows/debug.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- stable-next
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
@@ -20,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
@@ -48,7 +50,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -68,7 +70,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -199,7 +201,7 @@ jobs:
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup QEMU for Docker Buildx
|
||||
@@ -39,6 +39,8 @@ jobs:
|
||||
with:
|
||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||
target: dist
|
||||
build-args: |
|
||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||
tags: |
|
||||
${{ steps.tag.outputs.latest }}
|
||||
${{ steps.tag.outputs.versioned }}
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -3,6 +3,7 @@ name: Lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable-next
|
||||
- main-next
|
||||
- dev-next
|
||||
paths-ignore:
|
||||
@@ -11,6 +12,7 @@ on:
|
||||
- '!.github/workflows/lint.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- stable-next
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
@@ -20,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
@@ -34,4 +36,6 @@ jobs:
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
install-mode: binary
|
||||
@@ -19,10 +19,12 @@ builds:
|
||||
- with_ech
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- linux_386
|
||||
- linux_amd64_v1
|
||||
- linux_amd64_v3
|
||||
- linux_arm64
|
||||
@@ -36,6 +38,36 @@ builds:
|
||||
- darwin_amd64_v3
|
||||
- darwin_arm64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
- id: legacy
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_ech
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOROOT=/nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/share/go
|
||||
gobinary: /nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/bin/go
|
||||
targets:
|
||||
- windows_amd64_v1
|
||||
- windows_386
|
||||
- darwin_amd64_v1
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
- id: android
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
@@ -54,6 +86,8 @@ builds:
|
||||
- with_wireguard
|
||||
- with_ech
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
@@ -90,6 +124,9 @@ snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
archives:
|
||||
- id: archive
|
||||
builds:
|
||||
- main
|
||||
- android
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
@@ -98,6 +135,17 @@ archives:
|
||||
files:
|
||||
- LICENSE
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
- id: archive-legacy
|
||||
builds:
|
||||
- legacy
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||
nfpms:
|
||||
- id: package
|
||||
package_name: sing-box
|
||||
@@ -110,6 +158,7 @@ nfpms:
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- archlinux
|
||||
priority: extra
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@@ -1,23 +1,27 @@
|
||||
FROM golang:1.21-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
ARG TARGETOS TARGETARCH
|
||||
ARG GOPROXY=""
|
||||
ENV GOPROXY ${GOPROXY}
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=$TARGETOS
|
||||
ENV GOARCH=$TARGETARCH
|
||||
RUN set -ex \
|
||||
&& apk add git build-base \
|
||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_clash_api,with_acme \
|
||||
&& go build -v -trimpath -tags \
|
||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||
-o /go/bin/sing-box \
|
||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||
./cmd/sing-box
|
||||
FROM alpine AS dist
|
||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
RUN set -ex \
|
||||
&& apk upgrade \
|
||||
&& apk add bash tzdata ca-certificates \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||
ENTRYPOINT ["sing-box"]
|
||||
ENTRYPOINT ["sing-box"]
|
||||
|
||||
25
Makefile
25
Makefile
@@ -3,7 +3,7 @@ COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||
TAGS_GO120 = with_quic,with_ech
|
||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||
@@ -61,9 +61,9 @@ proto_install:
|
||||
release:
|
||||
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
|
||||
mkdir dist/release
|
||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/*.pkg.tar.zst dist/release
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||
rm -r dist
|
||||
rm -r dist/release
|
||||
|
||||
release_install:
|
||||
go install -v github.com/goreleaser/goreleaser@latest
|
||||
@@ -73,18 +73,21 @@ update_android_version:
|
||||
go run ./cmd/internal/update_android_version
|
||||
|
||||
build_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:assembleRelease && ./gradlew --stop
|
||||
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew --stop
|
||||
|
||||
upload_android:
|
||||
mkdir -p dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/release/*.apk dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
||||
rm -rf dist/release_android
|
||||
|
||||
release_android: lib_android update_android_version build_android upload_android
|
||||
|
||||
publish_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
||||
|
||||
publish_android_appcenter:
|
||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||
|
||||
build_ios:
|
||||
cd ../sing-box-for-apple && \
|
||||
@@ -93,7 +96,7 @@ build_ios:
|
||||
|
||||
upload_ios_app_store:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist
|
||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
release_ios: build_ios upload_ios_app_store
|
||||
|
||||
@@ -104,7 +107,7 @@ build_macos:
|
||||
|
||||
upload_macos_app_store:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist
|
||||
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
release_macos: build_macos upload_macos_app_store
|
||||
|
||||
@@ -115,7 +118,7 @@ build_macos_independent:
|
||||
|
||||
notarize_macos_independent:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist
|
||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
wait_notarize_macos_independent:
|
||||
sleep 60
|
||||
@@ -141,7 +144,7 @@ build_tvos:
|
||||
|
||||
upload_tvos_app_store:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist
|
||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
release_tvos: build_tvos upload_tvos_app_store
|
||||
|
||||
@@ -149,10 +152,8 @@ update_apple_version:
|
||||
go run ./cmd/internal/update_apple_version
|
||||
|
||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
||||
rm -rf dist
|
||||
|
||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||
rm -rf dist
|
||||
|
||||
test:
|
||||
@go test -v ./... && \
|
||||
|
||||
104
adapter/conn_router.go
Normal file
104
adapter/conn_router.go
Normal file
@@ -0,0 +1,104 @@
|
||||
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)
|
||||
}
|
||||
@@ -75,3 +75,11 @@ func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||
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 {
|
||||
newMetadata = *metadata
|
||||
}
|
||||
return WithContext(ctx, &newMetadata), &newMetadata
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
mdns "github.com/miekg/dns"
|
||||
@@ -24,8 +22,7 @@ type Router interface {
|
||||
|
||||
FakeIPStore() FakeIPStore
|
||||
|
||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
ConnectionRouter
|
||||
|
||||
GeoIPReader() *geoip.Reader
|
||||
LoadGeosite(code string) (Rule, error)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -19,7 +18,6 @@ type V2RayServerTransport interface {
|
||||
type V2RayServerTransportHandler interface {
|
||||
N.TCPConnectionHandler
|
||||
E.Handler
|
||||
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
|
||||
}
|
||||
|
||||
type V2RayClientTransport interface {
|
||||
|
||||
@@ -69,7 +69,7 @@ func (s *Box) startOutbounds() error {
|
||||
}
|
||||
problemOutbound := outbounds[problemOutboundTag]
|
||||
if problemOutbound == nil {
|
||||
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
|
||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
|
||||
}
|
||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||
}
|
||||
|
||||
@@ -17,9 +17,6 @@ func ReadTag() (string, error) {
|
||||
}
|
||||
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
|
||||
version := badversion.Parse(currentTagRev[1:])
|
||||
if version.PreReleaseIdentifier == "" {
|
||||
version.Patch++
|
||||
}
|
||||
return version.String() + "-" + shortCommit, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,16 @@ func main() {
|
||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
|
||||
if updated0 || updated1 {
|
||||
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||
}
|
||||
var updated2 bool
|
||||
if macProjectVersion := os.Getenv("MACOS_PROJECT_VERSION"); macProjectVersion != "" {
|
||||
newContent, updated2 = findAndReplaceProjectVersion(objectsMap, newContent, []string{"SFM"}, macProjectVersion)
|
||||
if updated2 {
|
||||
log.Info("updated macos project version to ", macProjectVersion)
|
||||
}
|
||||
}
|
||||
if updated0 || updated1 || updated2 {
|
||||
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
|
||||
} else {
|
||||
log.Info("version not changed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +67,30 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi
|
||||
return projectContent, updated
|
||||
}
|
||||
|
||||
func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent string, directoryList []string, newVersion string) (string, bool) {
|
||||
objectKeyList := findObjectKeyByDirectory(objectsMap, directoryList)
|
||||
var updated bool
|
||||
for _, objectKey := range objectKeyList {
|
||||
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
|
||||
indexes := matchRegexp.FindStringIndex(projectContent)
|
||||
if len(indexes) < 2 {
|
||||
println(projectContent)
|
||||
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
|
||||
}
|
||||
indexStart := indexes[1]
|
||||
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
|
||||
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "CURRENT_PROJECT_VERSION = ") + 26
|
||||
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
|
||||
version := projectContent[versionStart:versionEnd]
|
||||
if version == newVersion {
|
||||
continue
|
||||
}
|
||||
updated = true
|
||||
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
|
||||
}
|
||||
return projectContent, updated
|
||||
}
|
||||
|
||||
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||
var objectKeyList []string
|
||||
for objectKey, object := range objectsMap {
|
||||
@@ -77,3 +108,24 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||
}
|
||||
return objectKeyList
|
||||
}
|
||||
|
||||
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
|
||||
var objectKeyList []string
|
||||
for objectKey, object := range objectsMap {
|
||||
buildSettings := object.(map[string]any)["buildSettings"]
|
||||
if buildSettings == nil {
|
||||
continue
|
||||
}
|
||||
infoPListFile := buildSettings.(map[string]any)["INFOPLIST_FILE"]
|
||||
if infoPListFile == nil {
|
||||
continue
|
||||
}
|
||||
for _, searchDirectory := range directoryList {
|
||||
if strings.HasPrefix(infoPListFile.(string), searchDirectory+"/") {
|
||||
objectKeyList = append(objectKeyList, objectKey)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return objectKeyList
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
var commandGenerate = &cobra.Command{
|
||||
@@ -22,8 +21,7 @@ var commandGenerate = &cobra.Command{
|
||||
func init() {
|
||||
commandGenerate.AddCommand(commandGenerateUUID)
|
||||
commandGenerate.AddCommand(commandGenerateRandom)
|
||||
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
||||
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
||||
|
||||
mainCommand.AddCommand(commandGenerate)
|
||||
}
|
||||
|
||||
@@ -92,48 +90,3 @@ func generateUUID() error {
|
||||
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
|
||||
return err
|
||||
}
|
||||
|
||||
var commandGenerateWireGuardKeyPair = &cobra.Command{
|
||||
Use: "wg-keypair",
|
||||
Short: "Generate WireGuard key pair",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateWireGuardKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateWireGuardKey() error {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
var commandGenerateRealityKeyPair = &cobra.Command{
|
||||
Use: "reality-keypair",
|
||||
Short: "Generate reality key pair",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateRealityKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateRealityKey() error {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
40
cmd/sing-box/cmd_generate_tls.go
Normal file
40
cmd/sing-box/cmd_generate_tls.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var flagGenerateTLSKeyPairMonths int
|
||||
|
||||
var commandGenerateTLSKeyPair = &cobra.Command{
|
||||
Use: "tls-keypair <server_name>",
|
||||
Short: "Generate TLS self sign key pair",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateTLSKeyPair(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, "months", "m", 1, "Valid months")
|
||||
commandGenerate.AddCommand(commandGenerateTLSKeyPair)
|
||||
}
|
||||
|
||||
func generateTLSKeyPair(serverName string) error {
|
||||
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.WriteString(string(privateKeyPem) + "\n")
|
||||
os.Stdout.WriteString(string(publicKeyPem) + "\n")
|
||||
return nil
|
||||
}
|
||||
40
cmd/sing-box/cmd_generate_vapid.go
Normal file
40
cmd/sing-box/cmd_generate_vapid.go
Normal file
@@ -0,0 +1,40 @@
|
||||
//go:build go1.20
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commandGenerateVAPIDKeyPair = &cobra.Command{
|
||||
Use: "vapid-keypair",
|
||||
Short: "Generate VAPID key pair",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateVAPIDKeyPair()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandGenerate.AddCommand(commandGenerateVAPIDKeyPair)
|
||||
}
|
||||
|
||||
func generateVAPIDKeyPair() error {
|
||||
privateKey, err := ecdh.P256().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n")
|
||||
return nil
|
||||
}
|
||||
61
cmd/sing-box/cmd_generate_wireguard.go
Normal file
61
cmd/sing-box/cmd_generate_wireguard.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
||||
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
||||
}
|
||||
|
||||
var commandGenerateWireGuardKeyPair = &cobra.Command{
|
||||
Use: "wg-keypair",
|
||||
Short: "Generate WireGuard key pair",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateWireGuardKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateWireGuardKey() error {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
var commandGenerateRealityKeyPair = &cobra.Command{
|
||||
Use: "reality-keypair",
|
||||
Short: "Generate reality key pair",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateRealityKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateRealityKey() error {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
|
||||
return nil
|
||||
}
|
||||
@@ -10,6 +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/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
|
||||
@@ -120,18 +121,18 @@ func mergeTLSInboundOptions(options *option.InboundTLSOptions) *option.InboundTL
|
||||
}
|
||||
if options.CertificatePath != "" {
|
||||
if content, err := os.ReadFile(options.CertificatePath); err == nil {
|
||||
options.Certificate = strings.Split(string(content), "\n")
|
||||
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
|
||||
}
|
||||
}
|
||||
if options.KeyPath != "" {
|
||||
if content, err := os.ReadFile(options.KeyPath); err == nil {
|
||||
options.Key = strings.Split(string(content), "\n")
|
||||
options.Key = trimStringArray(strings.Split(string(content), "\n"))
|
||||
}
|
||||
}
|
||||
if options.ECH != nil {
|
||||
if options.ECH.KeyPath != "" {
|
||||
if content, err := os.ReadFile(options.ECH.KeyPath); err == nil {
|
||||
options.ECH.Key = strings.Split(string(content), "\n")
|
||||
options.ECH.Key = trimStringArray(strings.Split(string(content), "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,13 +145,13 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
||||
}
|
||||
if options.CertificatePath != "" {
|
||||
if content, err := os.ReadFile(options.CertificatePath); err == nil {
|
||||
options.Certificate = strings.Split(string(content), "\n")
|
||||
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
|
||||
}
|
||||
}
|
||||
if options.ECH != nil {
|
||||
if options.ECH.ConfigPath != "" {
|
||||
if content, err := os.ReadFile(options.ECH.ConfigPath); err == nil {
|
||||
options.ECH.Config = strings.Split(string(content), "\n")
|
||||
options.ECH.Config = trimStringArray(strings.Split(string(content), "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,9 +160,15 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
||||
|
||||
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
||||
if options.PrivateKeyPath != "" {
|
||||
if content, err := os.ReadFile(options.PrivateKeyPath); err == nil {
|
||||
options.PrivateKey = strings.Split(string(content), "\n")
|
||||
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 {
|
||||
return common.Filter(array, func(it string) bool {
|
||||
return strings.TrimSpace(it) != ""
|
||||
})
|
||||
}
|
||||
|
||||
@@ -143,14 +143,16 @@ func create() (*box.Box, context.CancelFunc, error) {
|
||||
signal.Stop(osSignals)
|
||||
close(osSignals)
|
||||
}()
|
||||
|
||||
startCtx, finishStart := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
_, loaded := <-osSignals
|
||||
if loaded {
|
||||
cancel()
|
||||
closeMonitor(startCtx)
|
||||
}
|
||||
}()
|
||||
err = instance.Start()
|
||||
finishStart()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, nil, E.Cause(err, "start service")
|
||||
|
||||
@@ -17,7 +17,7 @@ func NewConn(conn net.Conn) (net.Conn, error) {
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := killerCheck()
|
||||
err := KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
@@ -1,20 +1,20 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
runtimeDebug "runtime/debug"
|
||||
"time"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
var (
|
||||
KillerEnabled bool
|
||||
MemoryLimit int64
|
||||
MemoryLimit uint64
|
||||
killerLastCheck time.Time
|
||||
)
|
||||
|
||||
func killerCheck() error {
|
||||
func KillerCheck() error {
|
||||
if !KillerEnabled {
|
||||
return nil
|
||||
}
|
||||
@@ -23,10 +23,7 @@ func killerCheck() error {
|
||||
return nil
|
||||
}
|
||||
killerLastCheck = nowTime
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
||||
if inuseMemory > MemoryLimit {
|
||||
if memory.Total() > MemoryLimit {
|
||||
Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
@@ -18,7 +18,7 @@ func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := killerCheck()
|
||||
err := KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -137,10 +137,12 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if !destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
} else {
|
||||
if destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
} else {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/bufio/deadline"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -44,7 +45,14 @@ func (d *DetourDialer) DialContext(ctx context.Context, network string, destinat
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.DialContext(ctx, network, destination)
|
||||
conn, err := dialer.DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if deadline.NeedAdditionalReadDeadline(conn) {
|
||||
conn = deadline.NewConn(conn)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *DetourDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
|
||||
@@ -29,7 +29,12 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
|
||||
}
|
||||
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
||||
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
||||
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
||||
dialer = NewResolveDialer(
|
||||
router,
|
||||
dialer,
|
||||
options.Detour == "" && !options.TCPFastOpen,
|
||||
domainStrategy,
|
||||
time.Duration(options.FallbackDelay))
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
@@ -16,14 +16,16 @@ import (
|
||||
|
||||
type ResolveDialer struct {
|
||||
dialer N.Dialer
|
||||
parallel bool
|
||||
router adapter.Router
|
||||
strategy dns.DomainStrategy
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||
return &ResolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
strategy,
|
||||
fallbackDelay,
|
||||
@@ -34,7 +36,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
metadata.Destination = destination
|
||||
metadata.Domain = ""
|
||||
@@ -48,14 +50,18 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||
if d.parallel {
|
||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||
} else {
|
||||
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
metadata.Destination = destination
|
||||
metadata.Domain = ""
|
||||
|
||||
158
common/humanize/bytes.go
Normal file
158
common/humanize/bytes.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var defaultSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
var memorysSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KiByte,
|
||||
"mb": MiByte,
|
||||
"gb": GiByte,
|
||||
"tb": TiByte,
|
||||
"pb": PiByte,
|
||||
"eb": EiByte,
|
||||
"": Byte,
|
||||
"k": KiByte,
|
||||
"m": MiByte,
|
||||
"g": GiByte,
|
||||
"t": TiByte,
|
||||
"p": PiByte,
|
||||
"e": EiByte,
|
||||
}
|
||||
|
||||
var (
|
||||
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
)
|
||||
|
||||
func Bytes(s uint64) string {
|
||||
return humanateBytes(s, 1000, defaultSizes)
|
||||
}
|
||||
|
||||
func MemoryBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, defaultSizes)
|
||||
}
|
||||
|
||||
func IBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, iSizes)
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, defaultSizeTable)
|
||||
}
|
||||
|
||||
func ParseMemoryBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, memorysSizeTable)
|
||||
}
|
||||
|
||||
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := sizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
@@ -1,21 +1,42 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-mux"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
||||
type Client = mux.Client
|
||||
|
||||
func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.OutboundMultiplexOptions) (*Client, error) {
|
||||
if !options.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
var brutalOptions mux.BrutalOptions
|
||||
if options.Brutal != nil && options.Brutal.Enabled {
|
||||
brutalOptions = mux.BrutalOptions{
|
||||
Enabled: true,
|
||||
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
|
||||
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
|
||||
}
|
||||
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
|
||||
return nil, E.New("brutal: invalid upload speed")
|
||||
}
|
||||
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
|
||||
return nil, E.New("brutal: invalid download speed")
|
||||
}
|
||||
}
|
||||
return mux.NewClient(mux.Options{
|
||||
Dialer: dialer,
|
||||
Logger: logger,
|
||||
Protocol: options.Protocol,
|
||||
MaxConnections: options.MaxConnections,
|
||||
MinStreams: options.MinStreams,
|
||||
MaxStreams: options.MaxStreams,
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-mux"
|
||||
)
|
||||
|
||||
type (
|
||||
Client = mux.Client
|
||||
)
|
||||
|
||||
var (
|
||||
Destination = mux.Destination
|
||||
HandleConnection = mux.HandleConnection
|
||||
)
|
||||
65
common/mux/router.go
Normal file
65
common/mux/router.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"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-mux"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
router adapter.ConnectionRouter
|
||||
service *mux.Service
|
||||
}
|
||||
|
||||
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
||||
if !options.Enabled {
|
||||
return router, nil
|
||||
}
|
||||
var brutalOptions mux.BrutalOptions
|
||||
if options.Brutal != nil && options.Brutal.Enabled {
|
||||
brutalOptions = mux.BrutalOptions{
|
||||
Enabled: true,
|
||||
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
|
||||
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
|
||||
}
|
||||
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
|
||||
return nil, E.New("brutal: invalid upload speed")
|
||||
}
|
||||
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
|
||||
return nil, E.New("brutal: invalid download speed")
|
||||
}
|
||||
}
|
||||
service, err := mux.NewService(mux.ServiceOptions{
|
||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||
return log.ContextWithNewID(ctx)
|
||||
},
|
||||
Logger: logger,
|
||||
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Router{router, service}, nil
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination == mux.Destination {
|
||||
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||
} else {
|
||||
return r.router.RouteConnection(ctx, conn, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
32
common/mux/v2ray_legacy.go
Normal file
32
common/mux/v2ray_legacy.go
Normal file
@@ -0,0 +1,32 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
var _ N.Dialer = (*Dialer)(nil)
|
||||
|
||||
type Dialer struct {
|
||||
N.Dialer
|
||||
}
|
||||
|
||||
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
conn, err := d.Dialer.DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var source M.Socksaddr
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata != nil {
|
||||
source = metadata.Source
|
||||
}
|
||||
if !source.IsValid() {
|
||||
source = M.SocksaddrFromNet(conn.LocalAddr())
|
||||
}
|
||||
if destination.Addr.Is6() {
|
||||
source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
|
||||
}
|
||||
h := proxyproto.HeaderProxyFromAddrs(1, source.TCPAddr(), destination.TCPAddr())
|
||||
_, err = h.WriteTo(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, E.Cause(err, "write proxy protocol header")
|
||||
}
|
||||
return conn, nil
|
||||
default:
|
||||
return d.Dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
std_bufio "bufio"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
net.Listener
|
||||
AcceptNoHeader bool
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (net.Conn, error) {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bufReader := std_bufio.NewReader(conn)
|
||||
header, err := proxyproto.Read(bufReader)
|
||||
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
||||
return nil, &Error{err}
|
||||
}
|
||||
if bufReader.Buffered() > 0 {
|
||||
cache := buf.NewSize(bufReader.Buffered())
|
||||
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
||||
if err != nil {
|
||||
return nil, &Error{err}
|
||||
}
|
||||
conn = bufio.NewCachedConn(conn, cache)
|
||||
}
|
||||
if header != nil {
|
||||
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
||||
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
|
||||
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
|
||||
}}, nil
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
var _ net.Error = (*Error)(nil)
|
||||
|
||||
type Error struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (e *Error) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *Error) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (p *LinuxSystemProxy) Enable() error {
|
||||
}
|
||||
}
|
||||
if p.hasKWriteConfig5 {
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "1")
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (p *LinuxSystemProxy) Enable() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "Authmode", "0")
|
||||
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (p *LinuxSystemProxy) Disable() error {
|
||||
}
|
||||
}
|
||||
if p.hasKWriteConfig5 {
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "0")
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -90,7 +90,10 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
||||
solver.DNSProvider = &cloudflare.Provider{
|
||||
APIToken: dnsOptions.CloudflareOptions.APIToken,
|
||||
}
|
||||
default:
|
||||
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
|
||||
}
|
||||
acmeConfig.DNS01Solver = &solver
|
||||
}
|
||||
if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
|
||||
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
||||
|
||||
@@ -11,22 +11,34 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificate, err := tls.X509KeyPair(publicKeyPem, privateKeyPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certificate, err
|
||||
}
|
||||
|
||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||
NotAfter: timeFunc().Add(time.Hour),
|
||||
NotAfter: expire,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
@@ -37,17 +49,13 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certifi
|
||||
}
|
||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
publicPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
|
||||
privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
|
||||
keyPair, err := tls.X509KeyPair(publicPem, privPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &keyPair, err
|
||||
publicKeyPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
|
||||
privateKeyPem = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
}
|
||||
if certificate == nil && key == nil && options.Insecure {
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateKeyPair(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
|
||||
53
common/uot/router.go
Normal file
53
common/uot/router.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package uot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
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"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
var _ adapter.ConnectionRouter = (*Router)(nil)
|
||||
|
||||
type Router struct {
|
||||
router adapter.ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
||||
return &Router{router, logger}
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
switch metadata.Destination.Fqdn {
|
||||
case uot.MagicAddress:
|
||||
request, err := uot.ReadRequest(conn)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read UoT request")
|
||||
}
|
||||
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
|
||||
return r.router.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
|
||||
case uot.LegacyMagicAddress:
|
||||
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
|
||||
metadata.Domain = metadata.Destination.Fqdn
|
||||
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
||||
}
|
||||
return r.router.RouteConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
@@ -96,33 +97,25 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
||||
return
|
||||
}
|
||||
defer instance.Close()
|
||||
|
||||
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() {
|
||||
start = time.Now()
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodHead, link, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
transport := &http.Transport{
|
||||
Dial: func(string, string) (net.Conn, error) {
|
||||
return instance, nil
|
||||
},
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Transport: transport,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return instance, nil
|
||||
},
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
resp, err := client.Do(req)
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
3
constant/speed.go
Normal file
3
constant/speed.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package constant
|
||||
|
||||
const MbpsToBps = 125000
|
||||
@@ -1,8 +1,9 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
V2RayTransportTypeHTTP = "http"
|
||||
V2RayTransportTypeWebsocket = "ws"
|
||||
V2RayTransportTypeQUIC = "quic"
|
||||
V2RayTransportTypeGRPC = "grpc"
|
||||
V2RayTransportTypeHTTP = "http"
|
||||
V2RayTransportTypeWebsocket = "ws"
|
||||
V2RayTransportTypeQUIC = "quic"
|
||||
V2RayTransportTypeGRPC = "grpc"
|
||||
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ package box
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
|
||||
@@ -5,7 +5,7 @@ package box
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
@@ -27,8 +27,8 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/badjson"
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
@@ -37,9 +37,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
var memObject badjson.JSONObject
|
||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||
memObject.Put("rss", rusageMaxRSS())
|
||||
|
||||
|
||||
@@ -1,3 +1,333 @@
|
||||
#### 1.7.0-beta.2
|
||||
|
||||
* Fix crash if TUIC inbound authentication failed
|
||||
* Update quic-go to v0.40.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.7.0-beta.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.3
|
||||
|
||||
* iOS/Android: Fix profile auto update
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.7.0-alpha.11
|
||||
|
||||
* iOS/Android: Fix profile auto update
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.7.0-alpha.10
|
||||
|
||||
* Fix tcp-brutal not working with TLS
|
||||
* Fix Android client not closing in some cases
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.1
|
||||
|
||||
* Our [Android client](/installation/clients/sfa) is now available in the Google Play Store ▶️
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.7.0-alpha.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.7.0-alpha.4
|
||||
|
||||
* Migrate multiplex and UoT server to inbound **1**
|
||||
* Add TCP Brutal support for multiplex **2**
|
||||
|
||||
**1**:
|
||||
|
||||
Starting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound options.
|
||||
|
||||
**2**
|
||||
|
||||
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server, see [TCP Brutal](/configuration/shared/tcp-brutal) for details.
|
||||
|
||||
#### 1.7.0-alpha.3
|
||||
|
||||
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Introduced in V2Ray 5.10.0.
|
||||
|
||||
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||
|
||||
#### 1.6.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
Important changes since 1.5:
|
||||
|
||||
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎
|
||||
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||
* Update brutal congestion control for Hysteria2
|
||||
* Add `brutal_debug` option for Hysteria2
|
||||
* Update legacy Hysteria protocol **2**
|
||||
* Add TLS self sign key pair generate command
|
||||
* Remove [Deprecated Features](/deprecated) by agreement
|
||||
|
||||
**1**:
|
||||
|
||||
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
|
||||
|
||||
**2**
|
||||
|
||||
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||
|
||||
#### 1.7.0-alpha.2
|
||||
|
||||
* Fix bugs introduced in 1.7.0-alpha.1
|
||||
|
||||
#### 1.7.0-alpha.1
|
||||
|
||||
* Add [exclude route support](/configuration/inbound/tun) for TUN inbound
|
||||
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen) **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
If enabled, for UDP proxy requests addressed to a domain,
|
||||
the original packet address will be sent in the response instead of the mapped domain.
|
||||
|
||||
This option is used for compatibility with clients that
|
||||
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||
|
||||
#### 1.5.5
|
||||
|
||||
* Fix IPv6 `auto_route` for Linux **1**
|
||||
* Add legacy builds for old Windows and macOS systems **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
|
||||
|
||||
**2**:
|
||||
|
||||
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
||||
|
||||
|
||||
#### 1.6.0-rc.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-rc.1
|
||||
|
||||
* Add legacy builds for old Windows and macOS systems **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
||||
|
||||
#### 1.6.0-beta.4
|
||||
|
||||
* Fix IPv6 `auto_route` for Linux **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
|
||||
|
||||
#### 1.5.4
|
||||
|
||||
* Fix Clash cache crash on arm32 devices
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-beta.3
|
||||
|
||||
* Update the legacy Hysteria protocol **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**
|
||||
|
||||
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||
|
||||
#### 1.6.0-beta.2
|
||||
|
||||
* Add TLS self sign key pair generate command
|
||||
* Update brutal congestion control for Hysteria2
|
||||
* Fix Clash cache crash on arm32 devices
|
||||
* Update golang.org/x/net to v0.17.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-beta.3
|
||||
|
||||
* Update the legacy Hysteria protocol **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**
|
||||
|
||||
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||
|
||||
#### 1.6.0-beta.2
|
||||
|
||||
* Add TLS self sign key pair generate command
|
||||
* Update brutal congestion control for Hysteria2
|
||||
* Fix Clash cache crash on arm32 devices
|
||||
* Update golang.org/x/net to v0.17.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.5.3
|
||||
|
||||
* Fix compatibility with Android 14
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-beta.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-alpha.5
|
||||
|
||||
* Fix compatibility with Android 14
|
||||
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||
This update is intended to fix a memory leak flaw in the new implementation introduced in 1.6.0-alpha.1 and may
|
||||
introduce new issues.
|
||||
|
||||
#### 1.6.0-alpha.4
|
||||
|
||||
* Add `brutal_debug` option for Hysteria2
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.5.2
|
||||
|
||||
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-alpha.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-alpha.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.5.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.6.0-alpha.1
|
||||
|
||||
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||
* Update quic-go to v0.39.0
|
||||
* Update gVisor to 20230814.0
|
||||
* Remove [Deprecated Features](/deprecated) by agreement
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
|
||||
|
||||
#### 1.5.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
Important changes since 1.4:
|
||||
|
||||
* Add TLS [ECH server](/configuration/shared/tls) support
|
||||
* Improve TLS TCH client configuration
|
||||
* Add TLS ECH key pair generator **1**
|
||||
* Add TLS ECH support for QUIC based protocols **2**
|
||||
* Add KDE support for the `set_system_proxy` option in HTTP inbound
|
||||
* Add Hysteria2 protocol support **3**
|
||||
* Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **4**
|
||||
* Add DNS01 challenge support for ACME TLS certificate issuer **5**
|
||||
* Add `merge` command **6**
|
||||
* Mark [Deprecated Features](/deprecated)
|
||||
|
||||
**1**:
|
||||
|
||||
Command: `sing-box generate ech-keypair <plain_server_name> [--pq-signature-schemes-enabled]`
|
||||
|
||||
**2**:
|
||||
|
||||
All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria[/2]`, `TUIC` and `V2ray QUIC transport`.
|
||||
|
||||
**3**:
|
||||
|
||||
See [Hysteria2 inbound](/configuration/inbound/hysteria2) and [Hysteria2 outbound](/configuration/outbound/hysteria2)
|
||||
|
||||
For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)
|
||||
|
||||
**4**:
|
||||
|
||||
Interrupt existing connections when the selected outbound has changed.
|
||||
|
||||
Only inbound connections are affected by this setting, internal connections will always be interrupted.
|
||||
|
||||
**5**:
|
||||
|
||||
Only `Alibaba Cloud DNS` and `Cloudflare` are supported, see [ACME Fields](/configuration/shared/tls#acme-fields)
|
||||
and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge).
|
||||
|
||||
**6**:
|
||||
|
||||
This command also parses path resources that appear in the configuration file and replaces them with embedded
|
||||
configuration, such as TLS certificates or SSH private keys.
|
||||
|
||||
#### 1.5.0-rc.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.4.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.5.0-rc.5
|
||||
|
||||
* Fixed an improper authentication vulnerability in the SOCKS5 inbound
|
||||
* Fixes and improvements
|
||||
|
||||
**Security Advisory**
|
||||
|
||||
This update fixes an improper authentication vulnerability in the sing-box SOCKS inbound. This vulnerability allows an
|
||||
attacker to craft special requests to bypass user authentication. All users exposing SOCKS servers with user
|
||||
authentication in an insecure environment are advised to update immediately.
|
||||
|
||||
此更新修复了 sing-box SOCKS 入站中的一个不正确身份验证漏洞。 该漏洞允许攻击者制作特殊请求来绕过用户身份验证。建议所有将使用用户认证的
|
||||
SOCKS 服务器暴露在不安全环境下的用户立更新。
|
||||
|
||||
#### 1.4.5
|
||||
|
||||
* Fixed an improper authentication vulnerability in the SOCKS5 inbound
|
||||
* Fixes and improvements
|
||||
|
||||
**Security Advisory**
|
||||
|
||||
This update fixes an improper authentication vulnerability in the sing-box SOCKS inbound. This vulnerability allows an
|
||||
attacker to craft special requests to bypass user authentication. All users exposing SOCKS servers with user
|
||||
authentication in an insecure environment are advised to update immediately.
|
||||
|
||||
此更新修复了 sing-box SOCKS 入站中的一个不正确身份验证漏洞。 该漏洞允许攻击者制作特殊请求来绕过用户身份验证。建议所有将使用用户认证的
|
||||
SOCKS 服务器暴露在不安全环境下的用户立更新。
|
||||
|
||||
#### 1.5.0-rc.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.5.0-beta.12
|
||||
|
||||
* Add `merge` command **1**
|
||||
@@ -9,7 +339,7 @@ This command also parses path resources that appear in the configuration file an
|
||||
configuration, such as TLS certificates or SSH private keys.
|
||||
|
||||
```
|
||||
Merge configuration
|
||||
Merge configurations
|
||||
|
||||
Usage:
|
||||
sing-box merge [output] [flags]
|
||||
@@ -95,7 +425,7 @@ For protocol description, please refer to [https://v2.hysteria.network](https://
|
||||
|
||||
**1**:
|
||||
|
||||
Command: `sing-box generate ech-keypair <plain_server_name> [-pq-signature-schemes-enabled]`
|
||||
Command: `sing-box generate ech-keypair <plain_server_name> [--pq-signature-schemes-enabled]`
|
||||
|
||||
**2**:
|
||||
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
}
|
||||
],
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "",
|
||||
"tls": {}
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -67,6 +68,12 @@ Commands the client to use the BBR flow control algorithm instead of Hysteria CC
|
||||
|
||||
Conflict with `up_mbps` and `down_mbps`.
|
||||
|
||||
#### tls
|
||||
|
||||
==Required==
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### masquerade
|
||||
|
||||
HTTP3 server behavior when authentication fails.
|
||||
@@ -78,8 +85,6 @@ HTTP3 server behavior when authentication fails.
|
||||
|
||||
A 404 page will be returned if empty.
|
||||
|
||||
#### tls
|
||||
#### brutal_debug
|
||||
|
||||
==Required==
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
Enable debug information logging for Hysteria Brutal CC.
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
}
|
||||
],
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "",
|
||||
"tls": {}
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,10 +62,16 @@ Hysteria 用户
|
||||
|
||||
#### ignore_client_bandwidth
|
||||
|
||||
命令客户端使用 BBR 流量控制算法而不是 Hysteria CC。
|
||||
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||
|
||||
与 `up_mbps` 和 `down_mbps` 冲突。
|
||||
|
||||
#### tls
|
||||
|
||||
==必填==
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### masquerade
|
||||
|
||||
HTTP3 服务器认证失败时的行为。
|
||||
@@ -76,8 +83,6 @@ HTTP3 服务器认证失败时的行为。
|
||||
|
||||
如果为空,则返回 404 页。
|
||||
|
||||
#### tls
|
||||
#### brutal_debug
|
||||
|
||||
==必填==
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
... // Listen Fields
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -23,7 +24,8 @@
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -41,7 +43,8 @@
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -83,48 +86,6 @@ Both if empty.
|
||||
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
||||
| other methods | any string |
|
||||
|
||||
### Listen Fields
|
||||
#### multiplex
|
||||
|
||||
#### listen
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
... // 监听字段
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -23,7 +24,8 @@
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -41,7 +43,8 @@
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -81,4 +84,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|---------------|------------------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
|
||||
| other methods | 任意字符串 |
|
||||
| other methods | 任意字符串 |
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"server_port": 8081
|
||||
}
|
||||
},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@@ -58,6 +59,10 @@ Fallback server configuration for specified ALPN.
|
||||
|
||||
If not empty, TLS fallback requests with ALPN not in this table will be rejected.
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"server_port": 8081
|
||||
}
|
||||
},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@@ -60,6 +61,10 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
如果不为空,ALPN 不在此列表中的 TLS 回退请求将被拒绝。
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
@@ -48,7 +48,7 @@ TUIC 用户密码
|
||||
|
||||
#### congestion_control
|
||||
|
||||
QUIC 流量控制算法
|
||||
QUIC 拥塞控制算法
|
||||
|
||||
可选值: `cubic`, `new_reno`, `bbr`
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "system",
|
||||
"include_interface": [
|
||||
@@ -130,6 +136,14 @@ Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
!!! info ""
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "system",
|
||||
"include_interface": [
|
||||
@@ -131,6 +137,14 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
启用独立于端点的 NAT。
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@@ -49,6 +50,10 @@ Available values:
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@@ -49,6 +50,10 @@ VLESS 子协议。
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@@ -44,6 +45,10 @@ VMess users.
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@@ -44,6 +45,10 @@ VMess 用户。
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"brutal_debug": false,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@@ -73,6 +74,10 @@ Both is enabled by default.
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
Enable debug information logging for Hysteria Brutal CC.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial) for details.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"brutal_debug": false,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -43,7 +44,7 @@
|
||||
|
||||
最大带宽。
|
||||
|
||||
如果为空,将使用 BBR 流量控制算法而不是 Hysteria CC。
|
||||
如果为空,将使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||
|
||||
#### obfs.type
|
||||
|
||||
@@ -73,6 +74,9 @@ QUIC 流量混淆器密码.
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ Conflict with `multiplex`.
|
||||
|
||||
#### multiplex
|
||||
|
||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ UDP over TCP 配置。
|
||||
|
||||
#### multiplex
|
||||
|
||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
#### multiplex
|
||||
|
||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
#### multiplex
|
||||
|
||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ TUIC 用户密码
|
||||
|
||||
#### congestion_control
|
||||
|
||||
QUIC 流量控制算法
|
||||
QUIC 拥塞控制算法
|
||||
|
||||
可选值: `cubic`, `new_reno`, `bbr`
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
|
||||
... // Dial Fields
|
||||
@@ -68,6 +69,10 @@ UDP packet encoding, xudp is used by default.
|
||||
| packetaddr | Supported by v2ray 5+ |
|
||||
| xudp | Supported by xray |
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
|
||||
... // 拨号字段
|
||||
@@ -68,6 +69,10 @@ UDP 包编码,默认使用 xudp。
|
||||
| packetaddr | 由 v2ray 5+ 支持 |
|
||||
| xudp | 由 xray 支持 |
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
"multiplex": {},
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@@ -96,7 +96,7 @@ UDP packet encoding.
|
||||
|
||||
#### multiplex
|
||||
|
||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ UDP 包编码。
|
||||
|
||||
#### multiplex
|
||||
|
||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
|
||||
@@ -7,28 +7,26 @@
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"udp_timeout": 300,
|
||||
"detour": "another-in",
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_timeout": "300ms",
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
"proxy_protocol": false,
|
||||
"proxy_protocol_accept_no_header": false,
|
||||
"detour": "another-in"
|
||||
"udp_disable_domain_unmapping": false
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Available Context |
|
||||
|-----------------------------------|-------------------------------------------------------------------|
|
||||
| `listen` | Needs to listen on TCP or UDP. |
|
||||
| `listen_port` | Needs to listen on TCP or UDP. |
|
||||
| `tcp_fast_open` | Needs to listen on TCP. |
|
||||
| `tcp_multi_path` | Needs to listen on TCP. |
|
||||
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
|
||||
| `proxy_protocol` | Needs to listen on TCP. |
|
||||
| `proxy_protocol_accept_no_header` | When `proxy_protocol` enabled |
|
||||
| Field | Available Context |
|
||||
|--------------------------------|-------------------------------------------------------------------|
|
||||
| `listen` | Needs to listen on TCP or UDP. |
|
||||
| `listen_port` | Needs to listen on TCP or UDP. |
|
||||
| `tcp_fast_open` | Needs to listen on TCP. |
|
||||
| `tcp_multi_path` | Needs to listen on TCP. |
|
||||
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
|
||||
| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. |
|
||||
|
||||
#### listen
|
||||
|
||||
@@ -56,6 +54,16 @@ Enable TCP Multi Path.
|
||||
|
||||
Enable UDP fragmentation.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
#### detour
|
||||
|
||||
If set, connections will be forwarded to the specified inbound.
|
||||
|
||||
Requires target inbound support, see [Injectable](/configuration/inbound/#fields).
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
@@ -82,20 +90,10 @@ If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
#### udp_timeout
|
||||
#### udp_disable_domain_unmapping
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
If enabled, for UDP proxy requests addressed to a domain,
|
||||
the original packet address will be sent in the response instead of the mapped domain.
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
||||
|
||||
#### proxy_protocol_accept_no_header
|
||||
|
||||
Accept connections without Proxy Protocol header.
|
||||
|
||||
#### detour
|
||||
|
||||
If set, connections will be forwarded to the specified inbound.
|
||||
|
||||
Requires target inbound support, see [Injectable](/configuration/inbound/#fields).
|
||||
This option is used for compatibility with clients that
|
||||
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||
|
||||
@@ -7,14 +7,13 @@
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"udp_timeout": 300,
|
||||
"detour": "another-in",
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_timeout": "300ms",
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
"proxy_protocol": false,
|
||||
"proxy_protocol_accept_no_header": false,
|
||||
"detour": "another-in"
|
||||
"udp_disable_domain_unmapping": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,8 +25,7 @@
|
||||
| `tcp_fast_open` | 需要监听 TCP。 |
|
||||
| `tcp_multi_path` | 需要监听 TCP。 |
|
||||
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
|
||||
| `proxy_protocol` | 需要监听 TCP。 |
|
||||
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
|
||||
|
|
||||
|
||||
### 字段
|
||||
|
||||
@@ -57,6 +55,16 @@
|
||||
|
||||
启用 UDP 分段。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
|
||||
#### detour
|
||||
|
||||
如果设置,连接将被转发到指定的入站。
|
||||
|
||||
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。
|
||||
|
||||
#### sniff
|
||||
|
||||
启用协议探测。
|
||||
@@ -83,20 +91,8 @@
|
||||
|
||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
||||
|
||||
#### udp_timeout
|
||||
#### udp_disable_domain_unmapping
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
||||
|
||||
#### proxy_protocol_accept_no_header
|
||||
|
||||
接受没有代理协议标头的连接。
|
||||
|
||||
#### detour
|
||||
|
||||
如果设置,连接将被转发到指定的入站。
|
||||
|
||||
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。
|
||||
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
### Server Requirements
|
||||
### Inbound
|
||||
|
||||
`sing-box` :)
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Structure
|
||||
### Outbound
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -11,11 +17,27 @@
|
||||
"max_connections": 4,
|
||||
"min_streams": 4,
|
||||
"max_streams": 0,
|
||||
"padding": false
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
### Inbound Fields
|
||||
|
||||
#### enabled
|
||||
|
||||
Enable multiplex support.
|
||||
|
||||
#### padding
|
||||
|
||||
If enabled, non-padded connections will be rejected.
|
||||
|
||||
#### brutal
|
||||
|
||||
See [TCP Brutal](/configuration/shared/tcp-brutal) for details.
|
||||
|
||||
### Outbound Fields
|
||||
|
||||
#### enabled
|
||||
|
||||
@@ -59,3 +81,6 @@ Conflict with `max_connections` and `min_streams`.
|
||||
|
||||
Enable padding.
|
||||
|
||||
#### brutal
|
||||
|
||||
See [TCP Brutal](/configuration/shared/tcp-brutal) for details.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
### 服务器要求
|
||||
### 入站
|
||||
|
||||
`sing-box` :)
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 结构
|
||||
### 出站
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -10,11 +16,27 @@
|
||||
"protocol": "smux",
|
||||
"max_connections": 4,
|
||||
"min_streams": 4,
|
||||
"max_streams": 0
|
||||
"max_streams": 0,
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
### 入站字段
|
||||
|
||||
#### enabled
|
||||
|
||||
启用多路复用支持。
|
||||
|
||||
#### padding
|
||||
|
||||
如果启用,将拒绝非填充连接。
|
||||
|
||||
#### brutal
|
||||
|
||||
参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal)。
|
||||
|
||||
### 出站字段
|
||||
|
||||
#### enabled
|
||||
|
||||
@@ -58,3 +80,6 @@
|
||||
|
||||
启用填充。
|
||||
|
||||
#### brutal
|
||||
|
||||
参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal)。
|
||||
28
docs/configuration/shared/tcp-brutal.md
Normal file
28
docs/configuration/shared/tcp-brutal.md
Normal file
@@ -0,0 +1,28 @@
|
||||
### Server Requirements
|
||||
|
||||
* Linux
|
||||
* `brutal` congestion control algorithm kernel module installed
|
||||
|
||||
See [tcp-brutal](https://github.com/apernet/tcp-brutal) for details.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### enabled
|
||||
|
||||
Enable TCP Brutal congestion control algorithm。
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
==Required==
|
||||
|
||||
Upload and download bandwidth, in Mbps.
|
||||
28
docs/configuration/shared/tcp-brutal.zh.md
Normal file
28
docs/configuration/shared/tcp-brutal.zh.md
Normal file
@@ -0,0 +1,28 @@
|
||||
### 服务器要求
|
||||
|
||||
* Linux
|
||||
* `brutal` 拥塞控制算法内核模块已安装
|
||||
|
||||
参阅 [tcp-brutal](https://github.com/apernet/tcp-brutal)。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### enabled
|
||||
|
||||
启用 TCP Brutal 拥塞控制算法。
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
==必填==
|
||||
|
||||
上传和下载带宽,以 Mbps 为单位。
|
||||
@@ -233,7 +233,7 @@ Chrome fingerprint will be used if empty.
|
||||
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
|
||||
message.
|
||||
|
||||
The ECH key and configuration can be generated by `sing-box generate ech-keypair [-pq-signature-schemes-enabled]`.
|
||||
The ECH key and configuration can be generated by `sing-box generate ech-keypair [--pq-signature-schemes-enabled]`.
|
||||
|
||||
#### pq_signature_schemes_enabled
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其
|
||||
信息。
|
||||
|
||||
|
||||
ECH 配置和密钥可以通过 `sing-box generate ech-keypair [-pq-signature-schemes-enabled]` 生成。
|
||||
ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。
|
||||
|
||||
#### pq_signature_schemes_enabled
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ Available transports:
|
||||
* WebSocket
|
||||
* QUIC
|
||||
* gRPC
|
||||
* HTTPUpgrade
|
||||
|
||||
!!! warning "Difference from v2ray-core"
|
||||
|
||||
@@ -184,3 +185,32 @@ In standard gRPC client:
|
||||
If enabled, the client transport sends keepalive pings even with no active connections. If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive pings will be sent.
|
||||
|
||||
Disabled by default.
|
||||
|
||||
### HTTPUpgrade
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "httpupgrade",
|
||||
"host": "",
|
||||
"path": "",
|
||||
"headers": {}
|
||||
}
|
||||
```
|
||||
|
||||
#### host
|
||||
|
||||
Host domain.
|
||||
|
||||
The server will verify if not empty.
|
||||
|
||||
#### path
|
||||
|
||||
Path of HTTP request.
|
||||
|
||||
The server will verify if not empty.
|
||||
|
||||
#### headers
|
||||
|
||||
Extra headers of HTTP request.
|
||||
|
||||
The server will write in response if not empty.
|
||||
|
||||
@@ -14,6 +14,7 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议
|
||||
* WebSocket
|
||||
* QUIC
|
||||
* gRPC
|
||||
* HTTPUpgrade
|
||||
|
||||
!!! warning "与 v2ray-core 的区别"
|
||||
|
||||
@@ -183,3 +184,32 @@ gRPC 服务名称。
|
||||
如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用,则在没有活动连接时,将忽略 `idle_timeout` 和 `ping_timeout`,并且不会发送 keepalive ping。
|
||||
|
||||
默认禁用。
|
||||
|
||||
### HTTPUpgrade
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "httpupgrade",
|
||||
"host": "",
|
||||
"path": "",
|
||||
"headers": {}
|
||||
}
|
||||
```
|
||||
|
||||
#### host
|
||||
|
||||
主机域名。
|
||||
|
||||
默认服务器将验证。
|
||||
|
||||
#### path
|
||||
|
||||
HTTP 请求路径
|
||||
|
||||
默认服务器将验证。
|
||||
|
||||
#### headers
|
||||
|
||||
HTTP 请求的额外标头。
|
||||
|
||||
默认服务器将写入响应。
|
||||
|
||||
@@ -8,7 +8,7 @@ Experimental Android client for sing-box.
|
||||
|
||||
#### Download
|
||||
|
||||
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
|
||||
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)
|
||||
* [Github Releases](https://github.com/SagerNet/sing-box/releases)
|
||||
|
||||
#### Note
|
||||
@@ -16,3 +16,8 @@ Experimental Android client for sing-box.
|
||||
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||
* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
|
||||
* Crash logs is located in `$working_directory/stderr.log`
|
||||
|
||||
#### Privacy policy
|
||||
|
||||
* SFA did not collect or share personal data.
|
||||
* The data generated by the software is always on your device.
|
||||
|
||||
@@ -16,3 +16,8 @@
|
||||
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
|
||||
* 崩溃日志位于 `$working_directory/stderr.log`
|
||||
|
||||
#### 隐私政策
|
||||
|
||||
* SFA 不收集或共享个人数据。
|
||||
* 软件生成的数据始终在您的设备上。
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#### 注意事项
|
||||
|
||||
* 远程配置文件请求中的 User Agent 为 `SFI/$version ($version_code; sing-box $sing_box_version)`
|
||||
* 崩溃日志位于 `设置` -> `查看服务日志`
|
||||
* 崩溃日志位于 `Settings` -> `View Service Log`
|
||||
|
||||
#### 隐私政策
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#### 注意事项
|
||||
|
||||
* 远程配置文件请求中的 User Agent 为 `SFM/$version ($version_code; sing-box $sing_box_version)`
|
||||
* 崩溃日志位于 `设置` -> `查看服务日志`
|
||||
* 崩溃日志位于 `Settings` -> `View Service Log`
|
||||
|
||||
#### 隐私政策
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Experimental Apple tvOS client for sing-box.
|
||||
|
||||
#### Download
|
||||
|
||||
* [AppStore](https://apps.apple.com/us/app/sing-box/id6451272673)
|
||||
* [TestFlight](https://testflight.apple.com/join/AcqO44FH)
|
||||
|
||||
#### Features
|
||||
@@ -15,7 +16,7 @@ Experimental Apple tvOS client for sing-box.
|
||||
Full functionality, except for:
|
||||
|
||||
* Only remote configuration files can be created manually
|
||||
* You need to update SFI to the latest beta version to import profiles from iPhone/iPad
|
||||
* You need to update SFI to the latest version to import profiles from iPhone/iPad
|
||||
* No iCloud profile support
|
||||
|
||||
#### Note
|
||||
|
||||
31
docs/installation/clients/sft.zh.md
Normal file
31
docs/installation/clients/sft.zh.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# SFI
|
||||
|
||||
实验性的 Apple tvOS sing-box 客户端。
|
||||
|
||||
#### 要求
|
||||
|
||||
* tvOS 17.0+
|
||||
* 一个非中国大陆地区的 Apple 账号
|
||||
|
||||
#### 下载
|
||||
|
||||
* [AppStore](https://apps.apple.com/us/app/sing-box/id6451272673)
|
||||
* [TestFlight](https://testflight.apple.com/join/AcqO44FH)
|
||||
|
||||
#### 特性
|
||||
|
||||
完整的功能,除了:
|
||||
|
||||
* 只能手动创建远程配置文件
|
||||
* 您需要将 SFI 更新到最新版本才能从 iPhone/iPad 导入配置文件
|
||||
* 没有 iCloud 配置文件支持
|
||||
|
||||
#### 注意事项
|
||||
|
||||
* 远程配置文件请求中的 User Agent 为 `SFT/$version ($version_code; sing-box $sing_box_version)`
|
||||
* 崩溃日志位于 `Settings` -> `View Service Log`
|
||||
|
||||
#### 隐私政策
|
||||
|
||||
* SFT 不收集或共享个人数据。
|
||||
* 软件生成的数据始终在您的设备上。
|
||||
@@ -2,12 +2,14 @@ package clashapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
@@ -27,16 +29,16 @@ type Memory struct {
|
||||
|
||||
func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var conn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
@@ -63,13 +65,12 @@ func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package cachefile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/bbolt"
|
||||
bboltErrors "github.com/sagernet/bbolt/errors"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
|
||||
"go.etcd.io/bbolt"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,13 +44,25 @@ type CacheFile struct {
|
||||
func Open(path string, cacheID string) (*CacheFile, error) {
|
||||
const fileMode = 0o666
|
||||
options := bbolt.Options{Timeout: time.Second}
|
||||
db, err := bbolt.Open(path, fileMode, &options)
|
||||
switch err {
|
||||
case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch:
|
||||
if err = os.Remove(path); err != nil {
|
||||
var (
|
||||
db *bbolt.DB
|
||||
err error
|
||||
)
|
||||
for i := 0; i < 10; i++ {
|
||||
db, err = bbolt.Open(path, fileMode, &options)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
db, err = bbolt.Open(path, 0o666, &options)
|
||||
if errors.Is(err, bboltErrors.ErrTimeout) {
|
||||
continue
|
||||
}
|
||||
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
|
||||
rmErr := os.Remove(path)
|
||||
if rmErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,11 +5,10 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/bbolt"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const fakeipBucketPrefix = "fakeip_"
|
||||
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
@@ -25,13 +26,13 @@ func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manag
|
||||
|
||||
func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !websocket.IsWebSocketUpgrade(r) {
|
||||
if r.Header.Get("Upgrade") != "websocket" {
|
||||
snapshot := trafficManager.Snapshot()
|
||||
render.JSON(w, r, snapshot)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -56,7 +57,7 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
|
||||
if err := json.NewEncoder(buf).Encode(snapshot); err != nil {
|
||||
return err
|
||||
}
|
||||
return conn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
return wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err = sendSnapshot(); err != nil {
|
||||
|
||||
@@ -25,7 +25,8 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
@@ -314,7 +315,7 @@ func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// Browser websocket not support custom header
|
||||
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
|
||||
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token != serverSecret {
|
||||
render.Status(r, http.StatusUnauthorized)
|
||||
@@ -351,12 +352,6 @@ func hello(redirect bool) func(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type Traffic struct {
|
||||
Up int64 `json:"up"`
|
||||
Down int64 `json:"down"`
|
||||
@@ -364,16 +359,17 @@ type Traffic struct {
|
||||
|
||||
func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var conn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
@@ -392,11 +388,11 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
|
||||
break
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -432,16 +428,16 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
||||
}
|
||||
defer logFactory.UnSubscribe(subscription)
|
||||
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
var conn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
conn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
@@ -465,11 +461,11 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
234
experimental/libbox/build_info.go
Normal file
234
experimental/libbox/build_info.go
Normal file
@@ -0,0 +1,234 @@
|
||||
//go:build android
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"debug/buildinfo"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
const (
|
||||
androidVPNCoreTypeOpenVPN = "OpenVPN"
|
||||
androidVPNCoreTypeShadowsocks = "Shadowsocks"
|
||||
androidVPNCoreTypeClash = "Clash"
|
||||
androidVPNCoreTypeV2Ray = "V2Ray"
|
||||
androidVPNCoreTypeWireGuard = "WireGuard"
|
||||
androidVPNCoreTypeSingBox = "sing-box"
|
||||
androidVPNCoreTypeUnknown = "Unknown"
|
||||
)
|
||||
|
||||
type AndroidVPNType struct {
|
||||
CoreType string
|
||||
CorePath string
|
||||
GoVersion string
|
||||
}
|
||||
|
||||
func ReadAndroidVPNType(publicSourceDirList StringIterator) (*AndroidVPNType, error) {
|
||||
apkPathList := iteratorToArray[string](publicSourceDirList)
|
||||
var lastError error
|
||||
for _, apkPath := range apkPathList {
|
||||
androidVPNType, err := readAndroidVPNType(apkPath)
|
||||
if androidVPNType == nil {
|
||||
if err != nil {
|
||||
lastError = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return androidVPNType, nil
|
||||
}
|
||||
return nil, lastError
|
||||
}
|
||||
|
||||
func readAndroidVPNType(publicSourceDir string) (*AndroidVPNType, error) {
|
||||
reader, err := zip.OpenReader(publicSourceDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
var lastError error
|
||||
for _, file := range reader.File {
|
||||
if !strings.HasPrefix(file.Name, "lib/") {
|
||||
continue
|
||||
}
|
||||
vpnType, err := readAndroidVPNTypeEntry(file)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
return vpnType, nil
|
||||
}
|
||||
for _, file := range reader.File {
|
||||
if !strings.HasPrefix(file.Name, "lib/") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file.Name, androidVPNCoreTypeOpenVPN) || strings.Contains(file.Name, "ovpn") {
|
||||
return &AndroidVPNType{CoreType: androidVPNCoreTypeOpenVPN}, nil
|
||||
}
|
||||
if strings.Contains(file.Name, androidVPNCoreTypeShadowsocks) {
|
||||
return &AndroidVPNType{CoreType: androidVPNCoreTypeShadowsocks}, nil
|
||||
}
|
||||
}
|
||||
return nil, lastError
|
||||
}
|
||||
|
||||
func readAndroidVPNTypeEntry(zipFile *zip.File) (*AndroidVPNType, error) {
|
||||
readCloser, err := zipFile.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
libContent := make([]byte, zipFile.UncompressedSize64)
|
||||
_, err = io.ReadFull(readCloser, libContent)
|
||||
readCloser.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildInfo, err := buildinfo.Read(bytes.NewReader(libContent))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vpnType AndroidVPNType
|
||||
vpnType.GoVersion = buildInfo.GoVersion
|
||||
if !strings.HasPrefix(vpnType.GoVersion, "go") {
|
||||
vpnType.GoVersion = "obfuscated"
|
||||
} else {
|
||||
vpnType.GoVersion = vpnType.GoVersion[2:]
|
||||
}
|
||||
vpnType.CoreType = androidVPNCoreTypeUnknown
|
||||
if len(buildInfo.Deps) == 0 {
|
||||
vpnType.CoreType = "obfuscated"
|
||||
return &vpnType, nil
|
||||
}
|
||||
|
||||
dependencies := make(map[string]bool)
|
||||
dependencies[buildInfo.Path] = true
|
||||
for _, module := range buildInfo.Deps {
|
||||
dependencies[module.Path] = true
|
||||
if module.Replace != nil {
|
||||
dependencies[module.Replace.Path] = true
|
||||
}
|
||||
}
|
||||
for dependency := range dependencies {
|
||||
pkgType, loaded := determinePkgType(dependency)
|
||||
if loaded {
|
||||
vpnType.CoreType = pkgType
|
||||
}
|
||||
}
|
||||
if vpnType.CoreType == androidVPNCoreTypeUnknown {
|
||||
for dependency := range dependencies {
|
||||
pkgType, loaded := determinePkgTypeSecondary(dependency)
|
||||
if loaded {
|
||||
vpnType.CoreType = pkgType
|
||||
return &vpnType, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if vpnType.CoreType != androidVPNCoreTypeUnknown {
|
||||
vpnType.CorePath, _ = determineCorePath(buildInfo, vpnType.CoreType)
|
||||
return &vpnType, nil
|
||||
}
|
||||
if dependencies["github.com/golang/protobuf"] && dependencies["github.com/v2fly/ss-bloomring"] {
|
||||
vpnType.CoreType = androidVPNCoreTypeV2Ray
|
||||
return &vpnType, nil
|
||||
}
|
||||
return &vpnType, nil
|
||||
}
|
||||
|
||||
func determinePkgType(pkgName string) (string, bool) {
|
||||
pkgNameLower := strings.ToLower(pkgName)
|
||||
if strings.Contains(pkgNameLower, "clash") {
|
||||
return androidVPNCoreTypeClash, true
|
||||
}
|
||||
if strings.Contains(pkgNameLower, "v2ray") || strings.Contains(pkgNameLower, "xray") {
|
||||
return androidVPNCoreTypeV2Ray, true
|
||||
}
|
||||
|
||||
if strings.Contains(pkgNameLower, "sing-box") {
|
||||
return androidVPNCoreTypeSingBox, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func determinePkgTypeSecondary(pkgName string) (string, bool) {
|
||||
pkgNameLower := strings.ToLower(pkgName)
|
||||
if strings.Contains(pkgNameLower, "wireguard") {
|
||||
return androidVPNCoreTypeWireGuard, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func determineCorePath(pkgInfo *buildinfo.BuildInfo, pkgType string) (string, bool) {
|
||||
switch pkgType {
|
||||
case androidVPNCoreTypeClash:
|
||||
return determineCorePathForPkgs(pkgInfo, []string{"github.com/Dreamacro/clash"}, []string{"clash"})
|
||||
case androidVPNCoreTypeV2Ray:
|
||||
if v2rayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{
|
||||
"github.com/v2fly/v2ray-core",
|
||||
"github.com/v2fly/v2ray-core/v4",
|
||||
"github.com/v2fly/v2ray-core/v5",
|
||||
}, []string{
|
||||
"v2ray",
|
||||
}); loaded {
|
||||
return v2rayVersion, true
|
||||
}
|
||||
if xrayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{
|
||||
"github.com/xtls/xray-core",
|
||||
}, []string{
|
||||
"xray",
|
||||
}); loaded {
|
||||
return xrayVersion, true
|
||||
}
|
||||
return "", false
|
||||
case androidVPNCoreTypeSingBox:
|
||||
return determineCorePathForPkgs(pkgInfo, []string{"github.com/sagernet/sing-box"}, []string{"sing-box"})
|
||||
case androidVPNCoreTypeWireGuard:
|
||||
return determineCorePathForPkgs(pkgInfo, []string{"golang.zx2c4.com/wireguard"}, []string{"wireguard"})
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func determineCorePathForPkgs(pkgInfo *buildinfo.BuildInfo, pkgs []string, names []string) (string, bool) {
|
||||
for _, pkg := range pkgs {
|
||||
if pkgInfo.Path == pkg {
|
||||
return pkg, true
|
||||
}
|
||||
strictDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool {
|
||||
return module.Path == pkg
|
||||
})
|
||||
if strictDependency != nil {
|
||||
if isValidVersion(strictDependency.Version) {
|
||||
return strictDependency.Path + " " + strictDependency.Version, true
|
||||
} else {
|
||||
return strictDependency.Path, true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range names {
|
||||
if strings.Contains(pkgInfo.Path, name) {
|
||||
return pkgInfo.Path, true
|
||||
}
|
||||
looseDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool {
|
||||
return strings.Contains(module.Path, name) || (module.Replace != nil && strings.Contains(module.Replace.Path, name))
|
||||
})
|
||||
if looseDependency != nil {
|
||||
return looseDependency.Path, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isValidVersion(version string) bool {
|
||||
if version == "(devel)" {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(version, "v0.0.0") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -25,6 +25,7 @@ type CommandClientOptions struct {
|
||||
type CommandClientHandler interface {
|
||||
Connected()
|
||||
Disconnected(message string)
|
||||
ClearLog()
|
||||
WriteLog(message string)
|
||||
WriteStatus(message *StatusMessage)
|
||||
WriteGroups(message OutboundGroupIterator)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
runtimeDebug "runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
)
|
||||
|
||||
func (c *CommandClient) CloseConnections() error {
|
||||
|
||||
@@ -23,6 +23,9 @@ func readLog(reader io.Reader) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if messageLength == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
data := make([]byte, messageLength)
|
||||
_, err = io.ReadFull(reader, data)
|
||||
if err != nil {
|
||||
@@ -32,14 +35,24 @@ func readLog(reader io.Reader) ([]byte, error) {
|
||||
}
|
||||
|
||||
func writeLog(writer io.Writer, message []byte) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint16(len(message)))
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(message)
|
||||
err = binary.Write(writer, binary.BigEndian, uint16(len(message)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(message) > 0 {
|
||||
_, err = writer.Write(message)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func writeClearLog(writer io.Writer) error {
|
||||
return binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
var savedLines []string
|
||||
s.access.Lock()
|
||||
@@ -69,6 +82,11 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-s.logReset:
|
||||
err = writeClearLog(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
@@ -77,12 +95,24 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
|
||||
func (c *CommandClient) handleLogConn(conn net.Conn) {
|
||||
for {
|
||||
message, err := readLog(conn)
|
||||
var messageType uint8
|
||||
err := binary.Read(conn, binary.BigEndian, &messageType)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteLog(string(message))
|
||||
var message []byte
|
||||
switch messageType {
|
||||
case 0:
|
||||
message, err = readLog(conn)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteLog(string(message))
|
||||
case 1:
|
||||
c.handler.ClearLog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,16 @@ type CommandServer struct {
|
||||
handler CommandServerHandler
|
||||
|
||||
access sync.Mutex
|
||||
savedLines *list.List[string]
|
||||
savedLines list.List[string]
|
||||
maxLines int
|
||||
subscriber *observable.Subscriber[string]
|
||||
observer *observable.Observer[string]
|
||||
service *BoxService
|
||||
|
||||
// These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer
|
||||
urlTestUpdate chan struct{}
|
||||
modeUpdate chan struct{}
|
||||
logReset chan struct{}
|
||||
}
|
||||
|
||||
type CommandServerHandler interface {
|
||||
@@ -42,11 +44,11 @@ type CommandServerHandler interface {
|
||||
func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
|
||||
server := &CommandServer{
|
||||
handler: handler,
|
||||
savedLines: new(list.List[string]),
|
||||
maxLines: int(maxLines),
|
||||
subscriber: observable.NewSubscriber[string](128),
|
||||
urlTestUpdate: make(chan struct{}, 1),
|
||||
modeUpdate: make(chan struct{}, 1),
|
||||
logReset: make(chan struct{}, 1),
|
||||
}
|
||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||
return server
|
||||
@@ -56,6 +58,11 @@ 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)
|
||||
s.savedLines.Init()
|
||||
select {
|
||||
case s.logReset <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
s.service = newService
|
||||
s.notifyURLTestUpdate()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user